From 17d68750d9f24059fb45b621d80ca8d6a6a0334c Mon Sep 17 00:00:00 2001 From: David Gwynne Date: Mon, 9 Jan 2023 02:35:45 +1000 Subject: [PATCH] WIP Tuya MCU Bridge driver alternative to the TuyaMCU driver (#17626) * WIP Tuya MCU Bridge driver alternative to the TuyaMCU driver The main difference is this driver does not try and wire MCU data points (Dps) into the tasmota power/light/etc controls. Instead each Dp ends up being relayed directly to MQTT and the rules subsystem. If you want to change the state of something wired up to the MCU, you send tuyamcu specific commands to manipulate the Dp. Each Dp gets a type and id specific topic that is sent to MQTT. eg, Dp id 1 type bool looks like tele/%topic%/TUYAMCUBOOL1. To change state you send a TuyaMCUBool1 command (ie, the command index value is used as the DpId, which is nice and symmetrical) with the new value. Currently Rules operate on TuyaMCU#TypeDpid things, eg, "rule1 on TuyaMCU#Bool1 do power %value% endon" toggle the power on the tasmota device when the state of the thing on the MCU changes too. The most obviously missing stuff at the moment is: - better relaying of the wifi/mqtt status to the MCU - handling wifi reset requests from the MCU - low power stuff? - support for sending status updates and device info queries. - restarting the tuya mcu state machine? - restarting the rx state machine when no bytes are rxed for a period of time - time sync * shorten the log prefix to TYB (3 chars). requested by arendst * use the local definition for the SET_DP command. reaching back to the existing tuyamcu code isnt reliable. pointed out by arendst * put the todo list in the code so it can be tracked * check the wifi/mqtt state every second and update the mcu if it changes. * fix rule processing when Dp state is changed from a cmnd. rule processing was done as part of publishing the state, but publishing the state when it was updated by a command only happened if So59 was set. split rule processing out of publish and call them separately as needed. publish is now called from teleperiod, status updates from the MCU, and from cmnds if so59 is set. rules are called from status updates from the MCU and from cmnds. Co-authored-by: David Gwynne --- tasmota/berry/include/be_gpio_defines.h | 2 + tasmota/include/tasmota_configurations.h | 1 + tasmota/include/tasmota_template.h | 6 + tasmota/language/af_AF.h | 2 + tasmota/language/bg_BG.h | 2 + tasmota/language/ca_AD.h | 2 + tasmota/language/cs_CZ.h | 2 + tasmota/language/de_DE.h | 2 + tasmota/language/el_GR.h | 2 + tasmota/language/en_GB.h | 2 + tasmota/language/es_ES.h | 2 + tasmota/language/fr_FR.h | 2 + tasmota/language/fy_NL.h | 2 + tasmota/language/he_HE.h | 2 + tasmota/language/hu_HU.h | 2 + tasmota/language/it_IT.h | 2 + tasmota/language/ko_KO.h | 2 + tasmota/language/nl_NL.h | 2 + tasmota/language/pl_PL.h | 2 + tasmota/language/pt_BR.h | 2 + tasmota/language/pt_PT.h | 2 + tasmota/language/ro_RO.h | 2 + tasmota/language/ru_RU.h | 2 + tasmota/language/sk_SK.h | 2 + tasmota/language/sv_SE.h | 2 + tasmota/language/tr_TR.h | 2 + tasmota/language/uk_UA.h | 2 + tasmota/language/vi_VN.h | 2 + tasmota/language/zh_CN.h | 2 + tasmota/language/zh_TW.h | 2 + tasmota/my_user_config.h | 2 + tasmota/tasmota_support/support_features.ino | 5 +- .../.xdrv_65_tuyamcubr.ino.swp | Bin 0 -> 32768 bytes .../tasmota_xdrv_driver/xdrv_44_miel_hvac.ino | 4 + .../tasmota_xdrv_driver/xdrv_65_tuyamcubr.ino | 980 ++++++++++++++++++ tools/decode-status.py | 2 +- tools/lv_gpio/lv_gpio_enum.h | 2 + 37 files changed, 1056 insertions(+), 2 deletions(-) create mode 100644 tasmota/tasmota_xdrv_driver/.xdrv_65_tuyamcubr.ino.swp create mode 100644 tasmota/tasmota_xdrv_driver/xdrv_65_tuyamcubr.ino diff --git a/tasmota/berry/include/be_gpio_defines.h b/tasmota/berry/include/be_gpio_defines.h index ff68ac354..f037d9480 100644 --- a/tasmota/berry/include/be_gpio_defines.h +++ b/tasmota/berry/include/be_gpio_defines.h @@ -294,6 +294,8 @@ const be_const_member_t lv_gpio_constants[] = { { "TM1638STB", (int32_t) GPIO_TM1638STB }, { "TUYA_RX", (int32_t) GPIO_TUYA_RX }, { "TUYA_TX", (int32_t) GPIO_TUYA_TX }, + { "TUYAMCUBR_RX", (int32_t) GPIO_TUYAMCUBR_RX }, + { "TUYAMCUBR_TX", (int32_t) GPIO_TUYAMCUBR_TX }, { "TX2X_TXD_BLACK", (int32_t) GPIO_TX2X_TXD_BLACK }, { "TXD", (int32_t) GPIO_TXD }, { "VINDRIKTNING_RX", (int32_t) GPIO_VINDRIKTNING_RX }, diff --git a/tasmota/include/tasmota_configurations.h b/tasmota/include/tasmota_configurations.h index b787e1dcd..1ed060c54 100644 --- a/tasmota/include/tasmota_configurations.h +++ b/tasmota/include/tasmota_configurations.h @@ -41,6 +41,7 @@ #ifndef TUYA_DIMMER_ID #define TUYA_DIMMER_ID 0 // Default dimmer Id #endif +#define USE_TUYAMCUBR #undef USE_ARMTRONIX_DIMMERS // Disable support for Armtronix Dimmers (+1k4 code) #undef USE_PS_16_DZ // Disable support for PS-16-DZ Dimmer (+2k code) #undef USE_SONOFF_IFAN // Disable support for Sonoff iFan02 and iFan03 (+2k code) diff --git a/tasmota/include/tasmota_template.h b/tasmota/include/tasmota_template.h index 60a85240a..047e6766a 100644 --- a/tasmota/include/tasmota_template.h +++ b/tasmota/include/tasmota_template.h @@ -204,6 +204,7 @@ enum UserSelectablePins { GPIO_LD2410_TX, GPIO_LD2410_RX, // HLK-LD2410 GPIO_MBR_TX_ENA, GPIO_NRG_MBS_TX_ENA, // Modbus Bridge Serial Transmit Enable GPIO_ME007_TRIG, GPIO_ME007_RX, // ME007 Serial/Trigger interface + GPIO_TUYAMCUBR_TX, GPIO_TUYAMCUBR_RX, // TuyaMCU Bridge GPIO_SENSOR_END }; // Error as warning to rethink GPIO usage with max 2045 @@ -455,6 +456,7 @@ const char kSensorNames[] PROGMEM = D_SENSOR_LD2410_TX "|" D_SENSOR_LD2410_RX "|" D_SENSOR_MBR_TX_ENA "|" D_SENSOR_NRG_MBS_TX_ENA "|" D_SENSOR_ME007_TRIG "|" D_SENSOR_ME007_RX "|" + D_SENSOR_TUYAMCUBR_TX "|" D_SENSOR_TUYAMCUBR_RX "|" ; const char kSensorNamesFixed[] PROGMEM = @@ -1056,6 +1058,10 @@ const uint16_t kGpioNiceList[] PROGMEM = { AGPIO(GPIO_MIEL_HVAC_TX), // Mitsubishi Electric HVAC TX pin AGPIO(GPIO_MIEL_HVAC_RX), // Mitsubishi Electric HVAC RX pin #endif +#ifdef USE_TUYAMCUBR + AGPIO(GPIO_TUYAMCUBR_TX), + AGPIO(GPIO_TUYAMCUBR_RX), +#endif #ifdef USE_WIEGAND AGPIO(GPIO_WIEGAND_D0), // Date line D0 of Wiegand devices AGPIO(GPIO_WIEGAND_D1), // Date line D1 of Wiegand devices diff --git a/tasmota/language/af_AF.h b/tasmota/language/af_AF.h index 90d1cc897..7d1c464f1 100644 --- a/tasmota/language/af_AF.h +++ b/tasmota/language/af_AF.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index eacd7b06b..dfb0a19f2 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Дебитомер" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/ca_AD.h b/tasmota/language/ca_AD.h index cd7303851..3a3fca3fd 100644 --- a/tasmota/language/ca_AD.h +++ b/tasmota/language/ca_AD.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Cabal" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 2dda6f462..3aa103e51 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index a655df3ed..ed4d18686 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index 25a808e28..9b885d832 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 805cefd30..8bcdab91d 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 68d8da907..d5b64734e 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index 917389ff3..c1d2b1c72 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fy_NL.h b/tasmota/language/fy_NL.h index 70a30215b..cc4329c89 100644 --- a/tasmota/language/fy_NL.h +++ b/tasmota/language/fy_NL.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index 33360d150..de30f6352 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index 52f536ec4..00da79687 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index a962827bd..19a763846 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Portata" #define D_SENSOR_ME007_TRIG "ME007 - Tri" #define D_SENSOR_ME007_RX "ME007 - RX" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index d09b81e03..dcd06089e 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index 7d0624efb..ea9130f0f 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index 10c0b3b68..0e0415fb0 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index 1fce258ee..c7ee07fe3 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index af1763dc8..74ae4e72e 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index 0dd99d363..1e2f6f992 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 8de5385e5..893693054 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index 697020c42..9ede0cf21 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index d0607da30..1145c3f2c 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index dcb243c02..b43eaf75c 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index cc5c3de2f..1c72ce490 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h index ef0035a26..1ca96fd17 100644 --- a/tasmota/language/vi_VN.h +++ b/tasmota/language/vi_VN.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index b3f8cf5fe..5690e5018 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index a4fd10115..9f9e1959a 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -917,6 +917,8 @@ #define D_SENSOR_FLOWRATEMETER "Flowrate" #define D_SENSOR_ME007_TRIG "ME007 Tri" #define D_SENSOR_ME007_RX "ME007 Rx" +#define D_SENSOR_TUYAMCUBR_TX "TuyaMCUBr Tx" +#define D_SENSOR_TUYAMCUBR_RX "TuyaMCUBr Rx" // Units #define D_UNIT_AMPERE "安培" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 2c0e749f2..7933a9343 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -533,6 +533,7 @@ #define USE_TUYA_MCU // Add support for Tuya Serial MCU #define TUYA_DIMMER_ID 0 // Default dimmer Id #define USE_TUYA_TIME // Add support for Set Time in Tuya MCU +#define USE_TUYAMCUBR // Add support for TuyaMCU Bridge #define USE_ARMTRONIX_DIMMERS // Add support for Armtronix Dimmers (+1k4 code) #define USE_PS_16_DZ // Add support for PS-16-DZ Dimmer (+2k code) #define USE_SONOFF_IFAN // Add support for Sonoff iFan02 and iFan03 (+2k code) @@ -822,6 +823,7 @@ #define USE_TASMOTA_CLIENT_SERIAL_SPEED 57600 // Depends on the sketch that is running on the Uno/Pro Mini //#define USE_OPENTHERM // Add support for OpenTherm (+15k code) //#define USE_MIEL_HVAC // Add support for Mitsubishi Electric HVAC serial interface (+5k code) +//#define USE_TUYAMCUBR // Add support for TuyaMCU Bridge //#define USE_PROJECTOR_CTRL // Add support for LCD/DLP Projector serial control interface (+2k code) // #define USE_PROJECTOR_CTRL_NEC // Use codes for NEC // #define USE_PROJECTOR_CTRL_OPTOMA // Use codes for OPTOMA diff --git a/tasmota/tasmota_support/support_features.ino b/tasmota/tasmota_support/support_features.ino index 78a1d7d74..8f52b111e 100644 --- a/tasmota/tasmota_support/support_features.ino +++ b/tasmota/tasmota_support/support_features.ino @@ -867,7 +867,10 @@ void ResponseAppendFeatures(void) #if defined(USE_I2C) && defined(USE_PCA9632) feature9 |= 0x00002000; #endif -// feature9 |= 0x00004000; +#ifdef USE_TUYAMCUBR + feature9 |= 0x00004000; // xdrv_65_tuyamcubr.ino +#endif + // feature9 |= 0x00008000; // feature9 |= 0x00010000; diff --git a/tasmota/tasmota_xdrv_driver/.xdrv_65_tuyamcubr.ino.swp b/tasmota/tasmota_xdrv_driver/.xdrv_65_tuyamcubr.ino.swp new file mode 100644 index 0000000000000000000000000000000000000000..e97c202883be5731cc8c3788b63298369f39053e GIT binary patch literal 32768 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 AnE(I) literal 0 HcmV?d00001 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino b/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino index 60ea83e5a..65ab1ecdd 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_44_miel_hvac.ino @@ -24,10 +24,14 @@ #define XDRV_44 44 +#ifndef nitems #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif +#ifndef CTASSERT #define CTASSERT(x) extern char _ctassert[(x) ? 1 : -1 ] \ __attribute__((__unused__)) +#endif #define MIEL_HVAC_LOGNAME "MiElHVAC" diff --git a/tasmota/tasmota_xdrv_driver/xdrv_65_tuyamcubr.ino b/tasmota/tasmota_xdrv_driver/xdrv_65_tuyamcubr.ino new file mode 100644 index 000000000..c51c3daac --- /dev/null +++ b/tasmota/tasmota_xdrv_driver/xdrv_65_tuyamcubr.ino @@ -0,0 +1,980 @@ +/* + * xdrv_65_tuyamcubr.ino - TuyaMCU Bridge support for Tasmota + */ + +/* + * Copyright (C) 2023 David Gwynne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef USE_TUYAMCUBR + +/* + * Tuya MCU Bridge + */ + +/* + * TODO: + * + * - handling wifi reset requests from the MCU + * - low power stuff? + * - support for (re)sending status updates and device info queries + * - supporting the raw and string Dp types + * - restarting the tuya mcu state machine? + * - restarting the rx state machine when no bytes are rxed for a while + * - time sync + */ + +#define XDRV_65 65 + +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + +#ifndef CTASSERT +#define CTASSERT(x) extern char _ctassert[(x) ? 1 : -1 ] \ + __attribute__((__unused__)) +#endif + +#define TUYAMCUBR_LOGNAME "TYB" +#define TUYAMCUBR_FMT(_fmt) PSTR(TUYAMCUBR_LOGNAME ": " _fmt) + +#define D_CMND_TUYAMCUBR_PREFIX "TuyaMCU" + +#define D_CMND_TUYAMCUBR_DATA_RAW "Raw" +#define D_CMND_TUYAMCUBR_DATA_BOOL "Bool" +#define D_CMND_TUYAMCUBR_DATA_VALUE "Value" +#define D_CMND_TUYAMCUBR_DATA_STRING "String" +#define D_CMND_TUYAMCUBR_DATA_ENUM "Enum" + +#include + +struct tuyamcubr_header { + uint8_t header[2]; +#define TUYAMCUBR_H_ONE 0x55 +#define TUYAMCUBR_H_TWO 0xaa + uint8_t version; + uint8_t command; + uint16_t datalen; +}; + +CTASSERT(sizeof(struct tuyamcubr_header) == 6); + +#define TUYAMCUBR_CMD_HEARTBEAT 0x00 +#define TUYAMCUBR_CMD_PRODUCT 0x01 +#define TUYAMCUBR_CMD_MODE 0x02 +#define TUYAMCUBR_CMD_WIFI_STATE 0x03 +#define TUYAMCUBR_CMD_WIFI_RESET 0x04 +#define TUYAMCUBR_CMD_WIFI_SELECT 0x05 +#define TUYAMCUBR_CMD_SET_DP 0x06 +#define TUYAMCUBR_CMD_STATE 0x07 +#define TUYAMCUBR_CMD_QUERY_STATE 0x08 +#define TUYAMCUBR_CMD_INIT_UPGRADE 0x0a +#define TUYAMCUBR_CMD_UPGRADE_PKG 0x0b +#define TUYAMCUBR_CMD_SET_TIME 0x1c + +/* wifi state */ + +#define TUYAMCUBR_NETWORK_STATUS_1 0x00 /* pairing in EZ mode */ +#define TUYAMCUBR_NETWORK_STATUS_2 0x01 /* pairing in AP mode */ +#define TUYAMCUBR_NETWORK_STATUS_3 0x02 /* WiFi */ +#define TUYAMCUBR_NETWORK_STATUS_4 0x03 /* WiFi + router */ +#define TUYAMCUBR_NETWORK_STATUS_5 0x04 /* WiFi + router + cloud*/ +#define TUYAMCUBR_NETWORK_STATUS_6 0x05 /* low power mode */ +#define TUYAMCUBR_NETWORK_STATUS_7 0x06 /* pairing in EZ+AP mode */ + +/* set dp */ + +struct tuyamcubr_data_header { + uint8_t dpid; + uint8_t type; + uint16_t len; + + /* followed by len bytes */ +}; + +CTASSERT(sizeof(struct tuyamcubr_data_header) == 4); + +#define TUYAMCUBR_DATA_TYPE_RAW 0x00 +#define TUYAMCUBR_DATA_TYPE_BOOL 0x01 +#define TUYAMCUBR_DATA_TYPE_VALUE 0x02 +#define TUYAMCUBR_DATA_TYPE_STRING 0x03 +#define TUYAMCUBR_DATA_TYPE_ENUM 0x04 + +struct tuyamcubr_data_type { + const char *t_name; + int t_len; + uint32_t t_max; + uint32_t (*t_rd)(const uint8_t *); + void (*t_wr)(uint8_t *, uint32_t); +}; + +static uint32_t +tuyamcubr_rd_u8(const uint8_t *b) +{ + return (*b); +} + +static void +tuyamcubr_wr_u8(uint8_t *b, uint32_t v) +{ + *b = v; +} + +static uint32_t +tuyamcubr_rd_u32(const uint8_t *b) +{ + uint32_t be32; + memcpy(&be32, b, sizeof(be32)); + return (ntohl(be32)); +} + +static void +tuyamcubr_wr_u32(uint8_t *b, uint32_t v) +{ + uint32_t be32 = htonl(v); + memcpy(b, &be32, sizeof(be32)); +} + +static const struct tuyamcubr_data_type tuyamcubr_data_types[] = { + [TUYAMCUBR_DATA_TYPE_RAW] = { + .t_name = D_CMND_TUYAMCUBR_DATA_RAW, + .t_len = -1, + }, + [TUYAMCUBR_DATA_TYPE_BOOL] = { + .t_name = D_CMND_TUYAMCUBR_DATA_BOOL, + .t_len = 1, + .t_max = 1, + .t_rd = tuyamcubr_rd_u8, + .t_wr = tuyamcubr_wr_u8, + }, + [TUYAMCUBR_DATA_TYPE_VALUE] = { + .t_name = D_CMND_TUYAMCUBR_DATA_VALUE, + .t_len = sizeof(uint32_t), + .t_max = 0xffffffff, + .t_rd = tuyamcubr_rd_u32, + .t_wr = tuyamcubr_wr_u32, + }, + [TUYAMCUBR_DATA_TYPE_STRING] = { + .t_name = D_CMND_TUYAMCUBR_DATA_STRING, + .t_len = -1, + }, + [TUYAMCUBR_DATA_TYPE_ENUM] = { + .t_name = D_CMND_TUYAMCUBR_DATA_ENUM, + .t_len = 1, + .t_max = 0xff, + .t_rd = tuyamcubr_rd_u8, + .t_wr = tuyamcubr_wr_u8, + }, +}; + +static inline const struct tuyamcubr_data_type * +tuyamcubr_find_data_type(uint8_t type) +{ + const struct tuyamcubr_data_type *dt; + + if (type > nitems(tuyamcubr_data_types)) + return (NULL); + + dt = &tuyamcubr_data_types[type]; + if (dt->t_name == NULL) + return (NULL); + + return (dt); +} + +static inline uint8_t +tuyamcubr_cksum_fini(uint8_t sum) +{ + /* + * "Start from the header, add up all the bytes, and then divide + * the sum by 256 to get the remainder." + * + * If we accumulate bytes in a uint8_t, we get this for free. + */ + + return (sum); +} + +enum tuyamcubr_parser_state { + TUYAMCUBR_P_START, + TUYAMCUBR_P_HEADER, + TUYAMCUBR_P_VERSION, + TUYAMCUBR_P_COMMAND, + TUYAMCUBR_P_LEN1, + TUYAMCUBR_P_LEN2, + TUYAMCUBR_P_DATA, + TUYAMCUBR_P_CKSUM, + + TUYAMCUBR_P_SKIP, + TUYAMCUBR_P_SKIP_CKSUM, +}; + +//#ifdef ESP8266 +//#define TUYAMCUBR_BUFLEN 256 +//#else +#define TUYAMCUBR_BUFLEN 1024 +//#endif + +struct tuyamcubr_parser { + enum tuyamcubr_parser_state p_state; + unsigned int p_deadline; + + uint8_t p_version; + uint8_t p_command; + uint8_t p_sum; + + uint16_t p_len; + uint8_t p_off; + uint8_t p_data[TUYAMCUBR_BUFLEN]; +}; + +struct tuyamcubr_dp { + STAILQ_ENTRY(tuyamcubr_dp) dp_entry; + uint8_t dp_id; + uint8_t dp_type; + + uint32_t dp_value; +}; +STAILQ_HEAD(tuyamcubr_dps, tuyamcubr_dp); + +enum tuyamcubr_state { + TUYAMCUBR_S_START, + TUYAMCUBR_S_PROD_INFO, + TUYAMCUBR_S_MODE, + TUYAMCUBR_S_NET_STATUS, + TUYAMCUBR_S_RUNNING, +}; + +struct tuyamcubr_softc { + TasmotaSerial *sc_serial; + struct tuyamcubr_parser sc_parser; + + enum tuyamcubr_state sc_state; + unsigned int sc_deadline; + unsigned int sc_waiting; + uint8_t sc_network_status; + + unsigned int sc_clock; + struct tuyamcubr_dps sc_dps; +}; + +static struct tuyamcubr_softc *tuyamcubr_sc = nullptr; + +struct tuyamcubr_recv_command { + uint8_t r_command; + void (*r_func)(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); +}; + +static void tuyamcubr_recv_heartbeat(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); +static void tuyamcubr_recv_product_info(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); +static void tuyamcubr_recv_mode(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); +static void tuyamcubr_recv_net_status(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); +static void tuyamcubr_recv_status(struct tuyamcubr_softc *, uint8_t, + const uint8_t *, size_t); + +static const struct tuyamcubr_recv_command tuyamcubr_recv_commands[] = { + { TUYAMCUBR_CMD_HEARTBEAT, tuyamcubr_recv_heartbeat }, + { TUYAMCUBR_CMD_PRODUCT, tuyamcubr_recv_product_info }, + { TUYAMCUBR_CMD_MODE, tuyamcubr_recv_mode }, + { TUYAMCUBR_CMD_WIFI_STATE, tuyamcubr_recv_net_status }, + { TUYAMCUBR_CMD_STATE, tuyamcubr_recv_status }, +}; + +static void +tuyamcubr_recv(struct tuyamcubr_softc *sc, const struct tuyamcubr_parser *p) +{ + const struct tuyamcubr_recv_command *r; + const uint8_t *data = p->p_data; + size_t len = p->p_len; + size_t i; + + if (len > 0) { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("recv version 0x%02x command 0x%02x: %*_H"), + p->p_version, p->p_command, len, data); + } else { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("recv version 0x%02x command 0x%02x"), + p->p_version, p->p_command); + } + + for (i = 0; i < nitems(tuyamcubr_recv_commands); i++) { + r = &tuyamcubr_recv_commands[i]; + + if (r->r_command == p->p_command) { + r->r_func(sc, p->p_version, data, len); + return; + } + } + + /* unhandled command? */ +} + +static enum tuyamcubr_parser_state +tuyamcubr_parse(struct tuyamcubr_softc *sc, uint8_t byte) +{ + struct tuyamcubr_parser *p = &sc->sc_parser; + enum tuyamcubr_parser_state nstate = p->p_state; + + switch (p->p_state) { + case TUYAMCUBR_P_START: + if (byte != TUYAMCUBR_H_ONE) + return (TUYAMCUBR_P_START); + + /* reset state */ + p->p_sum = 0; + + nstate = TUYAMCUBR_P_HEADER; + break; + case TUYAMCUBR_P_HEADER: + if (byte != TUYAMCUBR_H_TWO) + return (TUYAMCUBR_P_START); + + nstate = TUYAMCUBR_P_VERSION; + break; + case TUYAMCUBR_P_VERSION: + p->p_version = byte; + nstate = TUYAMCUBR_P_COMMAND; + break; + case TUYAMCUBR_P_COMMAND: + p->p_command = byte; + nstate = TUYAMCUBR_P_LEN1; + break; + + case TUYAMCUBR_P_LEN1: + p->p_len = (uint16_t)byte << 8; + nstate = TUYAMCUBR_P_LEN2; + break; + case TUYAMCUBR_P_LEN2: + p->p_len |= (uint16_t)byte; + p->p_off = 0; + + if (p->p_len > sizeof(p->p_data)) { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("skipping command %02x" + ", too much data %zu/%zu"), p->p_command, + p->p_len, sizeof(p->p_data)); + return (TUYAMCUBR_P_SKIP); + } + + nstate = (p->p_len > 0) ? TUYAMCUBR_P_DATA : TUYAMCUBR_P_CKSUM; + break; + + case TUYAMCUBR_P_DATA: + p->p_data[p->p_off++] = byte; + if (p->p_off >= p->p_len) + nstate = TUYAMCUBR_P_CKSUM; + break; + + case TUYAMCUBR_P_CKSUM: + if (tuyamcubr_cksum_fini(p->p_sum) != byte) { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("checksum failed, skipping")); + return (TUYAMCUBR_P_START); + } + + tuyamcubr_recv(sc, p); + + /* this message is done, wait for another */ + return (TUYAMCUBR_P_START); + + case TUYAMCUBR_P_SKIP: + if (++p->p_off >= p->p_len) + return (TUYAMCUBR_P_SKIP_CKSUM); + return (nstate); + case TUYAMCUBR_P_SKIP_CKSUM: + return (TUYAMCUBR_P_START); + } + + p->p_sum += byte; + + return (nstate); +} + +static uint8_t +tuyamcubr_write(struct tuyamcubr_softc *sc, const void *data, size_t len) +{ + TasmotaSerial *serial = sc->sc_serial; + const uint8_t *bytes = (const uint8_t *)data; + uint8_t cksum = 0; + size_t i; + + for (i = 0; i < len; i++) { + uint8_t b = bytes[i]; + serial->write(b); + cksum += b; + } + + return (cksum); +} + +static void +tuyamcubr_send(struct tuyamcubr_softc *sc, uint8_t command, + const void *data, size_t len) +{ + TasmotaSerial *serial = sc->sc_serial; + struct tuyamcubr_header h = { + .header = { TUYAMCUBR_H_ONE, TUYAMCUBR_H_TWO }, + .version = 0x00, + .command = command, + .datalen = htons(len), + }; + uint8_t cksum = 0; + + if (len) { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("send version 0x%02x command 0x%02x: %*_H"), + h.version, h.command, len, data); + } else { + AddLog(LOG_LEVEL_DEBUG, + TUYAMCUBR_FMT("send version 0x%02x command 0x%02x"), + h.version, h.command); + } + + cksum += tuyamcubr_write(sc, &h, sizeof(h)); + if (len > 0) + cksum += tuyamcubr_write(sc, data, len); + cksum = tuyamcubr_cksum_fini(cksum); + serial->write(cksum); + serial->flush(); +} + +/* if we have polymorphic funcions then we may as well (ab)use them */ +static void +tuyamcubr_send(struct tuyamcubr_softc *sc, uint8_t command) +{ + tuyamcubr_send(sc, command, NULL, 0); +} + +static void +tuyamcubr_heartbeat(struct tuyamcubr_softc *sc, unsigned int deadline) +{ + sc->sc_deadline += deadline; + tuyamcubr_send(sc, TUYAMCUBR_CMD_HEARTBEAT); +} + +static struct tuyamcubr_dp * +tuyamcubr_find_dp(struct tuyamcubr_softc *sc, uint32_t index, uint8_t type) +{ + struct tuyamcubr_dp *dp; + + if (index > 0xff) + return (NULL); + + STAILQ_FOREACH(dp, &sc->sc_dps, dp_entry) { + if (dp->dp_id == index && + dp->dp_type == type) + return (dp); + } + + return (NULL); +} + +static void +tuyamcubr_cmnd_data(struct tuyamcubr_softc *sc, uint8_t type) +{ + const struct tuyamcubr_data_type *dt = &tuyamcubr_data_types[type]; + struct { + struct tuyamcubr_data_header h; + uint8_t value[4]; /* only up to 4 bytes */ + } data; + size_t len = sizeof(data.h) + dt->t_len; + struct tuyamcubr_dp *dp; + + dp = tuyamcubr_find_dp(sc, XdrvMailbox.index, type); + if (dp == NULL) { + ResponseCmndChar_P(PSTR("Unknown DpId")); + return; + } + + if (XdrvMailbox.data_len == 0) { + ResponseCmndNumber(dp->dp_value); + return; + } + + if (XdrvMailbox.payload < 0x00 || XdrvMailbox.payload > dt->t_max) { + ResponseCmndChar_P(PSTR("Invalid")); + return; + } + + dp->dp_value = XdrvMailbox.payload; + + data.h.dpid = dp->dp_id; + data.h.type = dp->dp_type; + data.h.len = htons(dt->t_len); + dt->t_wr(data.value, dp->dp_value); + + tuyamcubr_send(sc, TUYAMCUBR_CMD_SET_DP, &data, len); + tuyamcubr_rule_dp(sc, dp); + + ResponseCmndNumber(dp->dp_value); + + /* SetOption59 */ + if (Settings->flag3.hass_tele_on_power) + tuyamcubr_publish_dp(sc, dp); +} + +static void +tuyamcubr_cmnd_data_bool(void) +{ + tuyamcubr_cmnd_data(tuyamcubr_sc, TUYAMCUBR_DATA_TYPE_BOOL); +} + +static void +tuyamcubr_cmnd_data_value(void) +{ + tuyamcubr_cmnd_data(tuyamcubr_sc, TUYAMCUBR_DATA_TYPE_VALUE); +} + +static void +tuyamcubr_cmnd_data_enum(void) +{ + tuyamcubr_cmnd_data(tuyamcubr_sc, TUYAMCUBR_DATA_TYPE_ENUM); +} + +static void +tuyamcubr_rule_dp(struct tuyamcubr_softc *sc, const struct tuyamcubr_dp *dp) +{ + const struct tuyamcubr_data_type *dt = + &tuyamcubr_data_types[dp->dp_type]; + + /* XXX this only handles numeric types */ + + Response_P(PSTR("{\"%s\":{\"%s%u\":%u}}"), + D_CMND_TUYAMCUBR_PREFIX, + dt->t_name, dp->dp_id, + dp->dp_value); + XdrvRulesProcess(0); +} + +static void +tuyamcubr_publish_dp(struct tuyamcubr_softc *sc, const struct tuyamcubr_dp *dp) +{ + const struct tuyamcubr_data_type *dt = + &tuyamcubr_data_types[dp->dp_type]; + char topic[64]; /* how long is a (bit of) string? */ + + /* XXX this only handles numeric types */ + + snprintf(topic, sizeof(topic), PSTR("%s%s%u"), + D_CMND_TUYAMCUBR_PREFIX, dt->t_name, dp->dp_id); + Response_P(PSTR("%u"), dp->dp_value); + MqttPublishPrefixTopic_P(TELE, topic); +} + +static void +tuyamcubr_publish(struct tuyamcubr_softc *sc) +{ + struct tuyamcubr_dp *dp; + + STAILQ_FOREACH(dp, &sc->sc_dps, dp_entry) + tuyamcubr_publish_dp(sc, dp); +} + +static void +tuyamcubr_send_heartbeat(struct tuyamcubr_softc *sc, unsigned int deadline) +{ + sc->sc_deadline += deadline; + tuyamcubr_send(sc, TUYAMCUBR_CMD_HEARTBEAT); +} + +static void +tuyamcubr_recv_heartbeat(struct tuyamcubr_softc *sc, uint8_t v, + const uint8_t *data, size_t datalen) +{ + /* check the data? */ + + switch (sc->sc_state) { + case TUYAMCUBR_S_START: + sc->sc_state = TUYAMCUBR_S_PROD_INFO; + tuyamcubr_send(sc, TUYAMCUBR_CMD_PRODUCT); + break; + case TUYAMCUBR_S_RUNNING: + sc->sc_waiting = 0; + break; + default: + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unexpected heartbeat in state %u"), + sc->sc_state); + break; + } +} + +static void +tuyamcubr_recv_product_info(struct tuyamcubr_softc *sc, uint8_t v, + const uint8_t *data, size_t datalen) +{ + AddLog(LOG_LEVEL_INFO, TUYAMCUBR_FMT("MCU Product ID: %.*s"), + datalen, data); + + switch (sc->sc_state) { + case TUYAMCUBR_S_PROD_INFO: + sc->sc_state = TUYAMCUBR_S_MODE; + tuyamcubr_send(sc, TUYAMCUBR_CMD_MODE); + break; + default: + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unexpected product info in state %u"), + sc->sc_state); + break; + } +} + +static void +tuyamcubr_recv_mode(struct tuyamcubr_softc *sc, uint8_t v, + const uint8_t *data, size_t datalen) +{ + switch (sc->sc_state) { + case TUYAMCUBR_S_MODE: + switch (datalen) { + case 0: + AddLog(LOG_LEVEL_INFO, + TUYAMCUBR_FMT("MCU Mode: Coordinated")); + break; + case 2: + AddLog(LOG_LEVEL_INFO, TUYAMCUBR_FMT("MCU Mode" + ": Status GPIO%u, Reset GPIO%u"), + data[0], data[1]); + + sc->sc_state = TUYAMCUBR_S_RUNNING; + tuyamcubr_send(sc, TUYAMCUBR_CMD_QUERY_STATE); + return; + default: + AddLog(LOG_LEVEL_ERROR, TUYAMCUBR_FMT("MCU Mode" + ": unexpected data length %zu"), datalen); + break; + } + + sc->sc_state = TUYAMCUBR_S_NET_STATUS; + tuyamcubr_send(sc, TUYAMCUBR_CMD_WIFI_STATE, + &sc->sc_network_status, sizeof(sc->sc_network_status)); + break; + default: + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unexpected product info in state %u"), + sc->sc_state); + break; + } +} + +static void +tuyamcubr_recv_net_status(struct tuyamcubr_softc *sc, uint8_t v, + const uint8_t *data, size_t datalen) +{ + switch (sc->sc_state) { + case TUYAMCUBR_S_NET_STATUS: + sc->sc_state = TUYAMCUBR_S_RUNNING; + tuyamcubr_send(sc, TUYAMCUBR_CMD_QUERY_STATE); + break; + default: + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unexpected product info in state %u"), + sc->sc_state); + break; + } +} + +static void +tuyamcubr_recv_status(struct tuyamcubr_softc *sc, uint8_t v, + const uint8_t *data, size_t datalen) +{ + const struct tuyamcubr_data_type *dt; + struct tuyamcubr_dp *dp; + struct tuyamcubr_data_header h; + size_t len; + const uint8_t *b; + uint32_t value; + + /* take dp status updates at any time */ + + do { + if (datalen < sizeof(h)) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("status header short %zu<%zu"), + datalen, sizeof(h)); + return; + } + + memcpy(&h, data, sizeof(h)); + data += sizeof(h); + datalen -= sizeof(h); + + len = ntohs(h.len); + if (datalen < len) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("status data short %zu<%zu"), + datalen, len); + return; + } + + b = data; + data += len; + datalen -= len; + + dt = tuyamcubr_find_data_type(h.type); + if (dt == NULL || + dt->t_len == -1) { /* XXX revisit this */ + AddLog(LOG_LEVEL_INFO, + TUYAMCUBR_FMT("DpId %u unsupported type 0x%02x"), + h.dpid, h.type); + continue; + } + + if (len != dt->t_len) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("%s%s%u: unexpected len %zu"), + D_CMND_TUYAMCUBR_PREFIX, dt->t_name, len); + continue; + } + + value = dt->t_rd(b); + if (value > dt->t_max) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("%s%s%u: unexpected value %u>%u"), + D_CMND_TUYAMCUBR_PREFIX, dt->t_name, value, + dt->t_max); + continue; + } + + dp = tuyamcubr_find_dp(sc, h.dpid, h.type); + if (dp == NULL) { + dp = (struct tuyamcubr_dp *)malloc(sizeof(*dp)); + if (dp == NULL) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("%s%s%u no memory"), + D_CMND_TUYAMCUBR_PREFIX, + tuyamcubr_data_types[h.type], h.dpid); + continue; + } + + dp->dp_id = h.dpid; + dp->dp_type = h.type; + STAILQ_INSERT_TAIL(&sc->sc_dps, dp, dp_entry); + } else if (dp->dp_value == value) { + /* nop */ + continue; + } + + dp->dp_value = value; + tuyamcubr_rule_dp(sc, dp); + tuyamcubr_publish_dp(sc, dp); + } while (datalen > 0); +} + +static void +tuyamcubr_tick(struct tuyamcubr_softc *sc, unsigned int ms) +{ + int diff; + + sc->sc_clock += ms; + + diff = sc->sc_clock - sc->sc_deadline; + if (diff < 0) { + /* deadline hasn't been reached, nothing to do */ + return; + } + + switch (sc->sc_state) { + case TUYAMCUBR_S_START: + tuyamcubr_send_heartbeat(sc, 3000); + break; + case TUYAMCUBR_S_RUNNING: + tuyamcubr_send_heartbeat(sc, 15000); + if (sc->sc_waiting) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("no heartbeat response")); + /* XXX restart? */ + } + sc->sc_waiting = 1; + break; + } +} + +static void +tuyamcubr_every_1sec(struct tuyamcubr_softc *sc) +{ + /* start with the assumption that wifi is configured */ + uint8_t network_status = TUYAMCUBR_NETWORK_STATUS_3; + + if (MqttIsConnected()) { + /* the device is connected to the "cloud" */ + network_status = TUYAMCUBR_NETWORK_STATUS_5; + } else { + switch (WifiState()) { + case WIFI_MANAGER: + /* Pairing in AP mode */ + network_status = TUYAMCUBR_NETWORK_STATUS_2; + break; + case WIFI_RESTART: + /* WiFi + router */ + network_status = TUYAMCUBR_NETWORK_STATUS_4; + break; + } + } + + if (sc->sc_network_status != network_status) { + sc->sc_network_status = network_status; + + if (sc->sc_state == TUYAMCUBR_S_RUNNING) { + tuyamcubr_send(sc, TUYAMCUBR_CMD_WIFI_STATE, + &network_status, sizeof(network_status)); + } + } +} + +static void +tuyamcubr_pre_init(void) +{ + struct tuyamcubr_softc *sc; + int baudrate; + + /* + * SetOption97 - Set Baud rate for TuyaMCU serial communication + * (0 = 9600 or 1 = 115200) + */ + baudrate = (Settings->flag4.tuyamcu_baudrate) ? 115200 : 9600; + + if (!PinUsed(GPIO_TUYAMCUBR_TX) || !PinUsed(GPIO_TUYAMCUBR_RX)) + return; + + sc = (struct tuyamcubr_softc *)calloc(1, sizeof(*sc)); + if (sc == NULL) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unable to allocate state")); + return; + } + + sc->sc_parser.p_state = TUYAMCUBR_P_START; + + sc->sc_state = TUYAMCUBR_S_START; + sc->sc_clock = 0; + sc->sc_network_status = (WifiState() == WIFI_MANAGER) ? + TUYAMCUBR_NETWORK_STATUS_2 : TUYAMCUBR_NETWORK_STATUS_3; + STAILQ_INIT(&sc->sc_dps); + + sc->sc_serial = new TasmotaSerial(Pin(GPIO_TUYAMCUBR_RX), + Pin(GPIO_TUYAMCUBR_TX), 2); + if (!sc->sc_serial->begin(baudrate)) { + AddLog(LOG_LEVEL_ERROR, + TUYAMCUBR_FMT("unable to begin serial (baudrate %d)"), + baudrate); + goto del; + } + + if (sc->sc_serial->hardwareSerial()) + ClaimSerial(); + + /* commit */ + tuyamcubr_sc = sc; + + /* kick the state machine off */ + tuyamcubr_tick(sc, 0); + return; +del: + delete sc->sc_serial; +free: + free(sc); +} + +static void +tuyamcubr_loop(struct tuyamcubr_softc *sc) +{ + TasmotaSerial *serial = sc->sc_serial; + + while (serial->available()) { + yield(); + sc->sc_parser.p_state = tuyamcubr_parse(sc, serial->read()); + } +} + +/* + * Interface + */ + +static const char tuyamcubr_cmnd_names[] PROGMEM = + D_CMND_TUYAMCUBR_PREFIX + "|" D_CMND_TUYAMCUBR_DATA_BOOL + "|" D_CMND_TUYAMCUBR_DATA_VALUE + "|" D_CMND_TUYAMCUBR_DATA_ENUM + ; + +static void (*const tuyamcubr_cmnds[])(void) PROGMEM = { + &tuyamcubr_cmnd_data_bool, + &tuyamcubr_cmnd_data_value, + &tuyamcubr_cmnd_data_enum, +}; + +bool +Xdrv65(uint32_t function) +{ + bool result = false; + struct tuyamcubr_softc *sc; + + switch (function) { + case FUNC_PRE_INIT: + tuyamcubr_pre_init(); + return (false); + } + + sc = tuyamcubr_sc; + if (sc == NULL) + return (false); + + switch (function) { + case FUNC_LOOP: + tuyamcubr_loop(sc); + break; + +#if 0 + case FUNC_SET_DEVICE_POWER: + result = tuyamcubr_set_power(sc); + break; +#endif + + case FUNC_EVERY_100_MSECOND: + tuyamcubr_tick(sc, 100); + break; + + case FUNC_EVERY_50_MSECOND: + case FUNC_EVERY_200_MSECOND: + case FUNC_EVERY_250_MSECOND: + break; + case FUNC_EVERY_SECOND: + tuyamcubr_every_1sec(sc); + break; + +#if 0 + case FUNC_JSON_APPEND: + tuyamcubr_sensor(sc); + break; +#endif + case FUNC_AFTER_TELEPERIOD: + tuyamcubr_publish(sc); + break; + + case FUNC_COMMAND: + result = DecodeCommand(tuyamcubr_cmnd_names, tuyamcubr_cmnds); + break; + } + + return (result); +} + +#endif // USE_TUYAMCUBR diff --git a/tools/decode-status.py b/tools/decode-status.py index efc5d0d48..843a2d0e8 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -290,7 +290,7 @@ a_features = [[ "USE_SGP40","USE_LUXV30B","USE_CANSNIFFER","USE_QMC5883L", "USE_MODBUS_ENERGY","USE_SHELLY_PRO","USE_DALI","USE_BP1658CJ", "USE_DINGTIAN_RELAY","USE_HMC5883L","USE_LD2410","USE_ME007", - "USE_DISPLAY_TM1650","USE_PCA9632","","", + "USE_DISPLAY_TM1650","USE_PCA9632","USE_TUYAMCUBR","", "","","","", "","","","", "","","","", diff --git a/tools/lv_gpio/lv_gpio_enum.h b/tools/lv_gpio/lv_gpio_enum.h index d57740364..70b297099 100644 --- a/tools/lv_gpio/lv_gpio_enum.h +++ b/tools/lv_gpio/lv_gpio_enum.h @@ -209,6 +209,8 @@ ZIGBEE_RST = GPIO_ZIGBEE_RST DYP_RX = GPIO_DYP_RX MIEL_HVAC_TX = GPIO_MIEL_HVAC_TX MIEL_HVAC_RX = GPIO_MIEL_HVAC_RX +TUYAMCUBR_TX = GPIO_TUYAMCUBR_TX +TUYAMCUBR_RX = GPIO_TUYAMCUBR_RX WE517_TX = GPIO_WE517_TX WE517_RX = GPIO_WE517_RX AS608_TX = GPIO_AS608_TX