diff --git a/platformio.ini b/platformio.ini index 16553f0d6..552408790 100755 --- a/platformio.ini +++ b/platformio.ini @@ -56,6 +56,7 @@ default_envs = framework = arduino board = esp01_1m board_build.flash_mode = dout +board_build.ldscript = eagle.flash.1m.ld platform = ${core_active.platform} platform_packages = ${core_active.platform_packages} diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 93bbbb5ba..0c63c6e8f 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -135,10 +135,9 @@ build_flags = ${esp82xx_defaults.build_flags} [core_2_6_3] ; *** Esp8266 core for Arduino version 2.6.3 -platform = espressif8266@2.3.2 +platform = espressif8266@2.3.3 platform_packages = build_flags = ${esp82xx_defaults.build_flags} - -Wl,-Teagle.flash.1m.ld -DBEARSSL_SSL_BASIC ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 @@ -180,12 +179,10 @@ build_flags = ${esp82xx_defaults.build_flags} [tasmota_core_stage] ; *** Esp8266 core for Arduino version stable beta -platform = espressif8266@2.3.2 +platform = espressif8266@2.3.3 platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#6be561617f645f6a2ae82b8211f6af8c43e834cf build_flags = ${esp82xx_defaults.build_flags} - -Wl,-Teagle.flash.1m.ld -DBEARSSL_SSL_BASIC - -DNOPRINTFLOAT ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 @@ -226,12 +223,10 @@ build_flags = ${esp82xx_defaults.build_flags} [core_stage] ; *** Esp8266 core for Arduino version latest beta -platform = espressif8266@2.3.2 +platform = espressif8266@2.3.3 platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git -board_build.ldscript = eagle.flash.1m.ld build_flags = ${esp82xx_defaults.build_flags} -DBEARSSL_SSL_BASIC - -DNOPRINTFLOAT ; NONOSDK221 ; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK221 ; NONOSDK22x_190313 diff --git a/platformio_tasmota_env.ini b/platformio_tasmota_env.ini index 2a26ba4d2..cfcd53686 100644 --- a/platformio_tasmota_env.ini +++ b/platformio_tasmota_env.ini @@ -3,6 +3,7 @@ platform = ${common.platform} platform_packages = ${common.platform_packages} framework = ${common.framework} board = ${common.board} +board_build.ldscript = ${common.board_build.ldscript} board_build.flash_mode = ${common.board_build.flash_mode} board_build.f_cpu = ${common.board_build.f_cpu} build_unflags = ${common.build_unflags} diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 465bcde8f..ef9ce17f4 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -5,6 +5,8 @@ - Revert most wifi connectivity changes introduced in 8.1.0.5 (#7746, #7602, #7621) - Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596) - Add support for Wemos Motor Shield V1 by Denis Sborets (#7764) +- Fix Zigbee auto-increment transaction number (#7757) +- Add Zigbee enhanced commands decoding, added ``ZbPing`` ### 8.1.0.8 20200212 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 00cda53b4..ab7d18ce0 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -489,12 +489,15 @@ #define D_CMND_ZIGBEE_FORGET "Forget" #define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" + #define D_CMND_ZIGBEE_ENDPOINT "Endpoint" #define D_CMND_ZIGBEE_READ "Read" #define D_CMND_ZIGBEE_SEND "Send" #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" #define D_JSON_ZIGBEE_RECEIVED "ZbReceived" #define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived" #define D_CMND_ZIGBEE_BIND "Bind" +#define D_CMND_ZIGBEE_PING "Ping" + #define D_JSON_ZIGBEE_PING "ZbPing" // Commands xdrv_25_A4988_Stepper.ino #define D_CMND_MOTOR "MOTOR" diff --git a/tasmota/language/bg-BG.h b/tasmota/language/bg-BG.h index be2fceb3c..8acbff8e1 100644 --- a/tasmota/language/bg-BG.h +++ b/tasmota/language/bg-BG.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Посока на вятъра" +#define D_TX20_WIND_DEGREES "Степени на вятъра" #define D_TX20_WIND_SPEED "Скорост на вятъра" #define D_TX20_WIND_SPEED_AVG "Средна скорост на вятъра" +#define D_TX20_WIND_SPEED_MIN "Мини. скорост на вятъра" #define D_TX20_WIND_SPEED_MAX "Макс. скорост на вятъра" #define D_TX20_NORTH "С" #define D_TX20_EAST "И" diff --git a/tasmota/language/cs-CZ.h b/tasmota/language/cs-CZ.h index 6ef51f27d..2d15ac443 100644 --- a/tasmota/language/cs-CZ.h +++ b/tasmota/language/cs-CZ.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Směr větru" +#define D_TX20_WIND_DEGREES "Úhel větru" #define D_TX20_WIND_SPEED "Rychlost větru" #define D_TX20_WIND_SPEED_AVG "Průměrná rychlost větru" +#define D_TX20_WIND_SPEED_MIN "Minimální rychlost větru" #define D_TX20_WIND_SPEED_MAX "Maximální rychlost větru" #define D_TX20_NORTH "S" #define D_TX20_EAST "V" diff --git a/tasmota/language/de-DE.h b/tasmota/language/de-DE.h index cf2aa258b..e87624bfa 100644 --- a/tasmota/language/de-DE.h +++ b/tasmota/language/de-DE.h @@ -503,10 +503,12 @@ #define D_CALIBRATION "Kalibrierung" //xsns_35_tx20.ino -#define D_TX20_WIND_DIRECTION "Wind Richtung" +#define D_TX20_WIND_DIRECTION "Windrichtung" +#define D_TX20_WIND_DEGREES "Windrichtung Grad" #define D_TX20_WIND_SPEED "Windgeschwindigkeit" -#define D_TX20_WIND_SPEED_AVG "Ø Windgeschwindigkeit" -#define D_TX20_WIND_SPEED_MAX "max Windgeschwindigkeit" +#define D_TX20_WIND_SPEED_AVG "Windgeschwindigkeit Ø" +#define D_TX20_WIND_SPEED_MIN "Windgeschwindigkeit Min" +#define D_TX20_WIND_SPEED_MAX "Windgeschwindigkeit Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "O" #define D_TX20_SOUTH "S" diff --git a/tasmota/language/el-GR.h b/tasmota/language/el-GR.h index b3608bb7b..dbeb71a68 100644 --- a/tasmota/language/el-GR.h +++ b/tasmota/language/el-GR.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Κατεύθυνση ανέμου" +#define D_TX20_WIND_DEGREES "Βαθμός ανέμου" #define D_TX20_WIND_SPEED "Ταχύτητα ανέμου" #define D_TX20_WIND_SPEED_AVG "Μέση ταχύτητα ανέμου" +#define D_TX20_WIND_SPEED_MIN "Ελάχιστη ταχύτητα ανέμου" #define D_TX20_WIND_SPEED_MAX "Μέγιστη ταχύτητα ανέμου" #define D_TX20_NORTH "Β" #define D_TX20_EAST "Α" diff --git a/tasmota/language/en-GB.h b/tasmota/language/en-GB.h index 66a6e0243..792c2b597 100644 --- a/tasmota/language/en-GB.h +++ b/tasmota/language/en-GB.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Wind Direction" +#define D_TX20_WIND_DEGREES "Wind Degrees" #define D_TX20_WIND_SPEED "Wind Speed" #define D_TX20_WIND_SPEED_AVG "Wind Speed Avg" +#define D_TX20_WIND_SPEED_MIN "Wind Speed Min" #define D_TX20_WIND_SPEED_MAX "Wind Speed Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/es-ES.h b/tasmota/language/es-ES.h index 59916f487..77e7c6047 100644 --- a/tasmota/language/es-ES.h +++ b/tasmota/language/es-ES.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Dirección del Viento" +#define D_TX20_WIND_DEGREES "Ángulo del Viento" #define D_TX20_WIND_SPEED "Vel. del Viento" #define D_TX20_WIND_SPEED_AVG "Vel. Prom. del Viento" +#define D_TX20_WIND_SPEED_MIN "Vel. Min. del Viento" #define D_TX20_WIND_SPEED_MAX "Vel. Max. del Viento" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/fr-FR.h b/tasmota/language/fr-FR.h index 5356848d8..a63955ab7 100644 --- a/tasmota/language/fr-FR.h +++ b/tasmota/language/fr-FR.h @@ -504,8 +504,10 @@ //xsns_35_TX20.ino #define D_TX20_WIND_DIRECTION "Direction du vent" +#define D_TX20_WIND_DEGREES "Degré de vent" #define D_TX20_WIND_SPEED "Vitesse du vent" #define D_TX20_WIND_SPEED_AVG "Vitesse Moy." +#define D_TX20_WIND_SPEED_MIN "Vitesse Min" #define D_TX20_WIND_SPEED_MAX "Vitesse Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/he-HE.h b/tasmota/language/he-HE.h index fa3b07a31..86af3c35a 100644 --- a/tasmota/language/he-HE.h +++ b/tasmota/language/he-HE.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "כיוון הרוח" +#define D_TX20_WIND_DEGREES "זווית הרוח" #define D_TX20_WIND_SPEED "מהירות הרוח" #define D_TX20_WIND_SPEED_AVG "מהירות הרוח ממוצעת" +#define D_TX20_WIND_SPEED_MIN "מהירות הרוח היא מינימלית" #define D_TX20_WIND_SPEED_MAX "מהירות הרוח מקסימלית" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/hu-HU.h b/tasmota/language/hu-HU.h index 541d0c783..d9348dde1 100644 --- a/tasmota/language/hu-HU.h +++ b/tasmota/language/hu-HU.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Szélirány" +#define D_TX20_WIND_DEGREES "Szél mértéke" #define D_TX20_WIND_SPEED "Szélsebesség" #define D_TX20_WIND_SPEED_AVG "Átlag szélsebesség" +#define D_TX20_WIND_SPEED_MIN "Min. szélsebesség" #define D_TX20_WIND_SPEED_MAX "Max. szélsebesség" #define D_TX20_NORTH "É" #define D_TX20_EAST "K" diff --git a/tasmota/language/it-IT.h b/tasmota/language/it-IT.h index 62127ef24..e5ee2f1aa 100644 --- a/tasmota/language/it-IT.h +++ b/tasmota/language/it-IT.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Direzione Vento" +#define D_TX20_WIND_DEGREES "Angolo Vento" #define D_TX20_WIND_SPEED "Velocità Vento" #define D_TX20_WIND_SPEED_AVG "Velocità Media Vento" +#define D_TX20_WIND_SPEED_MIN "Velocità Minima Vento" #define D_TX20_WIND_SPEED_MAX "Velocità Massima Vento" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/ko-KO.h b/tasmota/language/ko-KO.h index 2a20ce2d0..f8cd83e99 100644 --- a/tasmota/language/ko-KO.h +++ b/tasmota/language/ko-KO.h @@ -504,9 +504,11 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "풍향" +#define D_TX20_WIND_DEGREES "바람 정도" #define D_TX20_WIND_SPEED "풍속" #define D_TX20_WIND_SPEED_AVG "평균 풍속" -#define D_TX20_WIND_SPEED_MAX "최대 풍속" +#define D_TX20_WIND_SPEED_MIN "풍속 최소" +#define D_TX20_WIND_SPEED_MAX "풍속 최대" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" #define D_TX20_SOUTH "S" diff --git a/tasmota/language/nl-NL.h b/tasmota/language/nl-NL.h index 658f18547..bc9b1876e 100644 --- a/tasmota/language/nl-NL.h +++ b/tasmota/language/nl-NL.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Windrichting" +#define D_TX20_WIND_DEGREES "Wind graad" #define D_TX20_WIND_SPEED "Windsnelheid" #define D_TX20_WIND_SPEED_AVG "Windsnelheid gemiddeld" +#define D_TX20_WIND_SPEED_MIN "Windsnelhied minimum" #define D_TX20_WIND_SPEED_MAX "Windsnelhied maximaal" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/pl-PL.h b/tasmota/language/pl-PL.h index e2d59a2dc..9cc522fa7 100644 --- a/tasmota/language/pl-PL.h +++ b/tasmota/language/pl-PL.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Kierunek" +#define D_TX20_WIND_DEGREES "Grad" #define D_TX20_WIND_SPEED "Prędkość" #define D_TX20_WIND_SPEED_AVG "Średnia prędkość" +#define D_TX20_WIND_SPEED_MIN "Minimalna prędkość" #define D_TX20_WIND_SPEED_MAX "Maksymalna prędkość" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/pt-BR.h b/tasmota/language/pt-BR.h index da386917c..4909fc08c 100644 --- a/tasmota/language/pt-BR.h +++ b/tasmota/language/pt-BR.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Direção do vento" +#define D_TX20_WIND_DEGREES "Ângulo do vento" #define D_TX20_WIND_SPEED "Velocidade do vento" #define D_TX20_WIND_SPEED_AVG "Velocidade média do vento" +#define D_TX20_WIND_SPEED_MIN "Velocidade do vento Mínima" #define D_TX20_WIND_SPEED_MAX "Velocidade do vento Máxima" #define D_TX20_NORTH "N" #define D_TX20_EAST "L" diff --git a/tasmota/language/pt-PT.h b/tasmota/language/pt-PT.h index 1f9c36a6b..1e49c449c 100644 --- a/tasmota/language/pt-PT.h +++ b/tasmota/language/pt-PT.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Direção do vento" +#define D_TX20_WIND_DEGREES "Ângulo do vento" #define D_TX20_WIND_SPEED "Velocidade do vento" #define D_TX20_WIND_SPEED_AVG "Velocidade média do vento" +#define D_TX20_WIND_SPEED_MIN "Velocidade mínima do vento" #define D_TX20_WIND_SPEED_MAX "Velocidade máxima do vento" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/ru-RU.h b/tasmota/language/ru-RU.h index 846a653ac..a312000b7 100644 --- a/tasmota/language/ru-RU.h +++ b/tasmota/language/ru-RU.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Wind Direction" +#define D_TX20_WIND_DEGREES "Wind Degrees" #define D_TX20_WIND_SPEED "Wind Speed" #define D_TX20_WIND_SPEED_AVG "Wind Speed Avg" +#define D_TX20_WIND_SPEED_MIN "Wind Speed Min" #define D_TX20_WIND_SPEED_MAX "Wind Speed Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/sk-SK.h b/tasmota/language/sk-SK.h index f335ca42d..f5041901a 100644 --- a/tasmota/language/sk-SK.h +++ b/tasmota/language/sk-SK.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Smer vetra" +#define D_TX20_WIND_DEGREES "Uhol vetra" #define D_TX20_WIND_SPEED "Rýchlosť vetra" #define D_TX20_WIND_SPEED_AVG "Priemerná rýchlosť vetra" +#define D_TX20_WIND_SPEED_MIN "Minimálna rýchlosť vetra" #define D_TX20_WIND_SPEED_MAX "Maximálna rýchlosť vetra" #define D_TX20_NORTH "S" #define D_TX20_EAST "V" diff --git a/tasmota/language/sv-SE.h b/tasmota/language/sv-SE.h index 9485c1f4e..7741c8937 100644 --- a/tasmota/language/sv-SE.h +++ b/tasmota/language/sv-SE.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Vindriktning" +#define D_TX20_WIND_DEGREES "Vindvinkel" #define D_TX20_WIND_SPEED "Vindstyrka" #define D_TX20_WIND_SPEED_AVG "Vindstyrka medel" +#define D_TX20_WIND_SPEED_MIN "Vindstyrka min" #define D_TX20_WIND_SPEED_MAX "Vindstyrka max" #define D_TX20_NORTH "N" #define D_TX20_EAST "Ö" diff --git a/tasmota/language/tr-TR.h b/tasmota/language/tr-TR.h index ee2a548ae..51f0a3ddb 100644 --- a/tasmota/language/tr-TR.h +++ b/tasmota/language/tr-TR.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Wind Direction" +#define D_TX20_WIND_DEGREES "Wind Degrees" #define D_TX20_WIND_SPEED "Wind Speed" #define D_TX20_WIND_SPEED_AVG "Wind Speed Avg" +#define D_TX20_WIND_SPEED_MIN "Wind Speed Min" #define D_TX20_WIND_SPEED_MAX "Wind Speed Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/language/uk-UA.h b/tasmota/language/uk-UA.h index 98a352ea7..a7bcfa5da 100644 --- a/tasmota/language/uk-UA.h +++ b/tasmota/language/uk-UA.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Напрям вітру" +#define D_TX20_WIND_DEGREES "Кут вітру" #define D_TX20_WIND_SPEED "Швидкість вітру" #define D_TX20_WIND_SPEED_AVG "Середня швидкість вітру" +#define D_TX20_WIND_SPEED_MIN "Мінімальна швидкість вітру" #define D_TX20_WIND_SPEED_MAX "Максимальна швидкість вітру" #define D_TX20_NORTH "Пн" #define D_TX20_EAST "Сх" diff --git a/tasmota/language/zh-CN.h b/tasmota/language/zh-CN.h index c46d353ea..53e05b1f4 100644 --- a/tasmota/language/zh-CN.h +++ b/tasmota/language/zh-CN.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "风向" +#define D_TX20_WIND_DEGREES "风度" #define D_TX20_WIND_SPEED "风速" #define D_TX20_WIND_SPEED_AVG "平均风速" +#define D_TX20_WIND_SPEED_MIN "最低风速" #define D_TX20_WIND_SPEED_MAX "最高风速" #define D_TX20_NORTH "北" #define D_TX20_EAST "东" diff --git a/tasmota/language/zh-TW.h b/tasmota/language/zh-TW.h index 118aeb501..2e9994046 100644 --- a/tasmota/language/zh-TW.h +++ b/tasmota/language/zh-TW.h @@ -504,8 +504,10 @@ //xsns_35_tx20.ino #define D_TX20_WIND_DIRECTION "Wind Direction" +#define D_TX20_WIND_DEGREES "Wind Degrees" #define D_TX20_WIND_SPEED "Wind Speed" #define D_TX20_WIND_SPEED_AVG "Wind Speed Avg" +#define D_TX20_WIND_SPEED_MIN "Wind Speed Min" #define D_TX20_WIND_SPEED_MAX "Wind Speed Max" #define D_TX20_NORTH "N" #define D_TX20_EAST "E" diff --git a/tasmota/settings.h b/tasmota/settings.h index 51151afd1..6d971a3de 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -104,7 +104,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa uint32_t zigbee_use_names : 1; // bit 1 (v8.1.0.4) - SetOption83 - Use FriendlyNames instead of ShortAddresses when possible uint32_t awsiot_shadow : 1; // bit 2 (v8.1.0.5) - SetOption84 - (AWS IoT) publish MQTT state to a device shadow - uint32_t spare03 : 1; + uint32_t device_groups_enabled : 1; // bit 3 (v8.1.0.9) - SetOption85 - Enable Device Groups uint32_t spare04 : 1; uint32_t spare05 : 1; uint32_t spare06 : 1; @@ -466,13 +466,14 @@ struct SYSCFG { uint8_t sps30_inuse_hours; // F02 uint8_t hotplug_scan; // F03 uint8_t reserved1; // F04 - reserved for s-hadinger - uint8_t free_f05[191]; // F05 - - uint32_t keeloq_master_msb; // FC4 - uint32_t keeloq_master_lsb; // FC8 - uint32_t keeloq_serial; // FCD - uint32_t keeloq_count; // FD0 + uint8_t free_f05[183]; // F05 + uint32_t keeloq_master_msb; // FBC + uint32_t keeloq_master_lsb; // FC0 + uint32_t keeloq_serial; // FC4 + uint32_t keeloq_count; // FC8 + uint32_t device_group_share_in; // FCC - Bitmask of device group items imported + uint32_t device_group_share_out; // FD0 - Bitmask of device group items exported uint32_t bootcount_reset_time; // FD4 int adc_param4; // FD8 uint32_t shutter_button[MAX_KEYS]; // FDC diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 33da93562..c2ddc6607 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -277,6 +277,23 @@ bool RtcRebootValid(void) /*********************************************************************************************\ * Config - Flash + * + * Tasmota 1M flash usage + * 0x00000000 - Unzipped binary bootloader + * 0x00001000 - Unzipped binary code start + * :::: + * 0x000xxxxx - Unzipped binary code end + * 0x000x1000 - First page used by Core OTA + * :::: + * 0x000F3000 - Tasmota Quick Power Cycle counter (SETTINGS_LOCATION - CFG_ROTATES) - First four bytes only + * 0x000F4000 - First Tasmota rotating settings page + * :::: + * 0x000FA000 - Last Tasmota rotating settings page = Last page used by Core OTA + * 0x000FB000 - Core SPIFFS end = Core EEPROM = Tasmota settings page during OTA and when no flash rotation is active (SETTINGS_LOCATION) + * 0x000FC000 - SDK - Uses first 128 bytes for phy init data mirrored by Core in RAM. See core_esp8266_phy.cpp phy_init_data[128] = Core user_rf_cal_sector + * 0x000FD000 - SDK - Uses scattered bytes from 0x340 (iTead use as settings storage from 0x000FD000) + * 0x000FE000 - SDK - Uses scattered bytes from 0x340 (iTead use as mirrored settings storage from 0x000FE000) + * 0x000FF000 - SDK - Uses at least first 32 bytes of this page - Tasmota Zigbee persistence from 0x000FF800 to 0x000FFFFF \*********************************************************************************************/ extern "C" { @@ -745,8 +762,17 @@ void SettingsErase(uint8_t type) _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK } - - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " %d " D_UNIT_SECTORS), _sectorEnd - _sectorStart); +#ifdef USE_WIFI_SDK_ERASE + else if (4 == type) { + _sectorStart = SETTINGS_LOCATION +1; // SDK phy area and Core calibration sector (0x0FC000) + _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0x0FCFFF) + } + else if (5 == type) { + _sectorStart = (ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE) -4; // SDK phy area and Core calibration sector (0xxFC000) + _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0xxFCFFF) + } +#endif // USE_WIFI_SDK_ERASE + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), _sectorStart * SPI_FLASH_SEC_SIZE, (_sectorEnd * SPI_FLASH_SEC_SIZE) -1); // EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely @@ -755,11 +781,21 @@ void SettingsErase(uint8_t type) void SettingsSdkErase(void) { - WiFi.disconnect(true); // Delete SDK wifi config + WiFi.disconnect(false); // Delete SDK wifi config SettingsErase(1); delay(1000); } +#ifdef USE_WIFI_SDK_ERASE +void SettingsSdkWifiErase(void) +{ + WiFi.disconnect(false); // Delete SDK wifi config + SettingsErase(4); + SettingsErase(5); + delay(200); +} +#endif // USE_WIFI_SDK_ERASE + /********************************************************************************************/ void SettingsDefault(void) diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index df509b9d3..866ca4eee 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -620,12 +620,17 @@ void WifiConnect(void) // Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset void WifiDisconnect(void) { +#ifdef USE_WIFI_SDK_ERASE // Do not enable with DeepSleep as it will wear out flash + SettingsSdkWifiErase(); +#else // Courtesy of EspEasy WiFi.persistent(true); // use SDK storage of SSID/WPA parameters ETS_UART_INTR_DISABLE(); wifi_station_disconnect(); // this will store empty ssid/wpa into sdk storage ETS_UART_INTR_ENABLE(); WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters + delay(100); // Flush anything in the network buffers. +#endif // USE_WIFI_SDK_ERASE } void WifiShutdown(void) diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index aadf44f71..1fda2a95a 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -398,7 +398,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain free(mqtt_save); bool result = MqttClient.publish(romram, mqtt_data, false); - AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram); yield(); // #3313 } #endif // USE_MQTT_AWS_IOT diff --git a/tasmota/xdrv_07_domoticz.ino b/tasmota/xdrv_07_domoticz.ino index 49e546b65..90563eeed 100644 --- a/tasmota/xdrv_07_domoticz.ino +++ b/tasmota/xdrv_07_domoticz.ino @@ -296,6 +296,7 @@ bool DomoticzMqttData(void) found = true; } else #endif // USE_SHUTTER +#ifdef USE_LIGHT if (iscolordimmer && 10 == nvalue) { // Color_SetColor // https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_light_to_a_certain_color_or_color_temperature JsonObject& color = domoticz["Color"]; @@ -333,8 +334,9 @@ bool DomoticzMqttData(void) snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER)); snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue); found = true; - } - else if (1 == nvalue || 0 == nvalue) { + } else +#endif // USE_LIGHT + if (1 == nvalue || 0 == nvalue) { if (((power >> i) &1) == (power_t)nvalue) { return true; // Stop loop } diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 101d5703d..0bc592266 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -21,7 +21,7 @@ // contains some definitions for functions used before their declarations -void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1); +void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId); // Get an JSON attribute, with case insensitive key search diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index 4814bdb8a..71752f4e3 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -19,6 +19,10 @@ #ifdef USE_ZIGBEE +#ifndef ZIGBEERECEIVED +#define ZIGBEERECEIVED 1 +#endif + #include #include @@ -49,6 +53,8 @@ typedef struct Z_Device { // json buffer used for attribute reporting DynamicJsonBuffer *json_buffer; JsonObject *json; + // sequence number for Zigbee frames + uint8_t seqNumber; } Z_Device; // All devices are stored in a Vector @@ -96,6 +102,9 @@ public: // device just seen on the network, update the lastSeen field void updateLastSeen(uint16_t shortaddr); + // get next sequence number for (increment at each all) + uint8_t getNextSeqNumber(uint16_t shortaddr); + // Dump json String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; @@ -133,6 +142,7 @@ public: private: std::vector _devices = {}; uint32_t _saveTimer = 0; + uint8_t _seqNumber = 0; // global seqNumber if device is unknown template < typename T> static bool findInVector(const std::vector & vecOfElements, const T & element); @@ -226,7 +236,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { std::vector(), 0,0,0,0, nullptr, - nullptr, nullptr }; + nullptr, nullptr, + 0, // seqNumber + }; device.json_buffer = new DynamicJsonBuffer(); _devices.push_back(device); dirty(); @@ -532,6 +544,19 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) { _updateLastSeen(device); } +// get the next sequance number for the device, or use the global seq number if device is unknown +uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) { + int32_t short_found = findShortAddr(shortaddr); + if (short_found >= 0) { + Z_Device &device = getShortAddr(shortaddr); + device.seqNumber += 1; + return device.seqNumber; + } else { + _seqNumber += 1; + return _seqNumber; + } +} + // Per device timers // // Reset the timer for a specific device @@ -704,18 +729,22 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#if ZIGBEERECEIVED // DEPRECATED TODO Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#endif } else { Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#if ZIGBEERECEIVED // DEPRECATED TODO Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); XdrvRulesProcess(); +#endif } // MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); // XdrvRulesProcess(); diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index d97e5e7d4..4debd73fd 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -486,18 +486,8 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { // Parse non-normalized attributes -// The key is "s_" followed by 16 bits clusterId, "_" followed by 8 bits command id void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { - uint32_t i = offset; - uint32_t len = _payload.len(); - - char attrid_str[12]; - snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id); - - char hex_char[_payload.len()*2+2]; - ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); - - json[attrid_str] = hex_char; + convertClusterSpecific(json, _cluster_id, _cmd_id, _frame_control.b.direction, _payload); } // return value: diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 073ab8e4c..5c3e3fc1b 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -19,34 +19,81 @@ #ifdef USE_ZIGBEE -//typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const char *new_name, void * param); typedef struct Z_CommandConverter { const char * tasmota_cmd; - const char * zcl_cmd; + uint16_t cluster; + uint8_t cmd; // normally 8 bits, 0xFF means it's a parameter + uint8_t direction; // direction of the command. 0x01 client->server, 0x02 server->client, 0x03 both + const char * param; } Z_CommandConverter; +typedef struct Z_XYZ_Var { // Holds values for vairables X, Y and Z + uint32_t x = 0; + uint32_t y = 0; + uint32_t z = 0; + uint8_t x_type = 0; // 0 = no value, 1 = 1 bytes, 2 = 2 bytes + uint8_t y_type = 0; + uint8_t z_type = 0; +} Z_XYZ_Var; + // list of post-processing directives const Z_CommandConverter Z_Commands[] = { - { "Power", "0006!xx" }, // 0=Off, 1=On, 2=Toggle - { "Dimmer", "0008!04/xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid) - { "Dimmer+", "0008!06/001902" }, // Step up by 10%, 0.2 secs - { "Dimmer-", "0008!06/011902" }, // Step down by 10%, 0.2 secs - { "DimmerStop", "0008!03" }, // Stop any Dimmer animation - { "ResetAlarm", "0009!00/xxyyyy" }, // Reset alarm (alarm code + cluster identifier) - { "ResetAllAlarms","0009!01" }, // Reset all alarms - { "Hue", "0300!00/xx000A00" }, // Move to Hue, shortest time, 1s - { "Sat", "0300!03/xx0A00" }, // Move to Sat - { "HueSat", "0300!06/xxyy0A00" }, // Hue, Sat - { "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16) - { "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16) - { "Shutter", "0102!xx" }, - { "ShutterOpen", "0102!00" }, - { "ShutterClose", "0102!01" }, - { "ShutterStop", "0102!02" }, - { "ShutterLift", "0102!05xx" }, // Lift percentage, 0%=open, 100%=closed - { "ShutterTilt", "0102!08xx" }, // Tilt percentage + // Group adress commands + { "AddGroup", 0x0004, 0x00, 0x01, "xxxx00" }, // Add group id, group name is not supported + { "ViewGroup", 0x0004, 0x01, 0x01, "xxxx" }, // Ask for the group name + { "GetGroup", 0x0004, 0x02, 0x01, "01xxxx" }, // Get one group membership + { "GetAllGroups", 0x0004, 0x02, 0x01, "00" }, // Get all groups membership + { "RemoveGroup", 0x0004, 0x03, 0x01, "xxxx" }, // Remove one group + { "RemoveAllGroups",0x0004, 0x04, 0x01, "" }, // Remove all groups + // Light & Shutter commands + { "Power", 0x0006, 0xFF, 0x01, "" }, // 0=Off, 1=On, 2=Toggle + { "Dimmer", 0x0008, 0x04, 0x01, "xx0A00" }, // Move to Level with On/Off, xx=0..254 (255 is invalid) + { "Dimmer+", 0x0008, 0x06, 0x01, "001902" }, // Step up by 10%, 0.2 secs + { "Dimmer-", 0x0008, 0x06, 0x01, "011902" }, // Step down by 10%, 0.2 secs + { "DimmerStop", 0x0008, 0x03, 0x01, "" }, // Stop any Dimmer animation + { "ResetAlarm", 0x0009, 0x00, 0x01, "xxyyyy" }, // Reset alarm (alarm code + cluster identifier) + { "ResetAllAlarms", 0x0009, 0x01, 0x01, "" }, // Reset all alarms + { "Hue", 0x0300, 0x00, 0x01, "xx000A00" }, // Move to Hue, shortest time, 1s + { "Sat", 0x0300, 0x03, 0x01, "xx0A00" }, // Move to Sat + { "HueSat", 0x0300, 0x06, 0x01, "xxyy0A00" }, // Hue, Sat + { "Color", 0x0300, 0x07, 0x01, "xxxxyyyy0A00" }, // x, y (uint16) + { "CT", 0x0300, 0x0A, 0x01, "xxxx0A00" }, // Color Temperature Mireds (uint16) + { "ShutterOpen", 0x0102, 0x00, 0x01, "" }, + { "ShutterClose", 0x0102, 0x01, 0x01, "" }, + { "ShutterStop", 0x0102, 0x02, 0x01, "" }, + { "ShutterLift", 0x0102, 0x05, 0x01, "xx" }, // Lift percentage, 0%=open, 100%=closed + { "ShutterTilt", 0x0102, 0x08, 0x01, "xx" }, // Tilt percentage + { "Shutter", 0x0102, 0xFF, 0x01, "" }, + // Blitzwolf PIR + { "Occupancy", 0xEF00, 0x01, 0x01, "xx"}, // Specific decoder for Blitzwolf PIR, empty name means special treatment + // Decoders only - normally not used to send, and names may be masked by previous definitions + { "Dimmer", 0x0008, 0x00, 0x01, "xx" }, + { "DimmerMove", 0x0008, 0x01, 0x01, "xx0A" }, + { "DimmerStep", 0x0008, 0x02, 0x01, "xx190A00" }, + { "DimmerMove", 0x0008, 0x05, 0x01, "xx0A" }, + { "Dimmer+", 0x0008, 0x06, 0x01, "00" }, + { "Dimmer-", 0x0008, 0x06, 0x01, "01" }, + { "DimmerStop", 0x0008, 0x07, 0x01, "" }, + { "HueMove", 0x0300, 0x01, 0x01, "xx19" }, + { "HueStep", 0x0300, 0x02, 0x01, "xx190A00" }, + { "SatMove", 0x0300, 0x04, 0x01, "xx19" }, + { "SatStep", 0x0300, 0x05, 0x01, "xx190A" }, + { "ColorMove", 0x0300, 0x08, 0x01, "xxxxyyyy" }, + { "ColorStep", 0x0300, 0x09, 0x01, "xxxxyyyy0A00" }, + // Tradfri + { "ArrowClick", 0x0005, 0x07, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right + { "ArrowHold", 0x0005, 0x08, 0x01, "xx" }, // xx == 0x01 = left, 0x00 = right + { "ArrowRelease", 0x0005, 0x09, 0x01, "" }, + // IAS - Intruder Alarm System + leak/fire detection + { "ZoneStatusChange",0x0500, 0x00, 0x02, "xxxxyyzz" }, // xxxx = zone status, yy = extended status, zz = zone id, Delay is ignored + // responses for Group cluster commands + { "AddGroupResp", 0x0004, 0x00, 0x02, "xxyyyy" }, // xx = status, yy = group id + { "ViewGroupResp", 0x0004, 0x01, 0x02, "xxyyyy" }, // xx = status, yy = group id, name ignored + { "GetGroupResp", 0x0004, 0x02, 0x02, "xxyyzzzz" }, // xx = capacity, yy = count, zzzz = first group id, following groups ignored + { "RemoveGroup", 0x0004, 0x03, 0x02, "xxyyyy" }, // xx = status, yy = group id }; + #define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian // Below are the attributes we wand to read from each cluster @@ -55,6 +102,7 @@ const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT +// This callback is registered after a cluster specific command and sends a read command for the same cluster int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { size_t attrs_len = 0; const uint8_t* attrs = nullptr; @@ -78,11 +126,10 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi break; } if (attrs) { - ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */); + ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr)); } } - // set a timer to read back the value in the future void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) { uint32_t wait_ms = 0; @@ -105,22 +152,182 @@ void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoi } } -const __FlashStringHelper* zigbeeFindCommand(const char *command) { - char parm_uc[16]; // used to convert JSON keys to uppercase +// returns true if char is 'x', 'y' or 'z' +inline bool isXYZ(char c) { + return (c >= 'x') && (c <= 'z'); +} + +// returns the Hex value of a digit [0-9A-Fa-f] +// return: 0x00-0x0F +// or -1 if cannot be parsed +inline int8_t hexValue(char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'A') && (c <= 'F')) { + return 10 + c - 'A'; + } + if ((c >= 'a') && (c <= 'f')) { + return 10 + c - 'a'; + } + return -1; +} + +// Parse a Big Endian suite of max_len digits, or stops when a non-hex digit is found +uint32_t parseHex_P(const char **data, size_t max_len = 8) { + uint32_t ret = 0; + for (uint32_t i = 0; i < max_len; i++) { + int8_t v = hexValue(pgm_read_byte(*data)); + if (v < 0) { break; } // non hex digit, we stop parsing + ret = (ret << 4) | v; + *data += 1; + } + return ret; +} + +// Parse a model like "xxyy00" +// and fill x, y and z values +// Little Endian encoding +// On exit, xyz is updated, and x_type, y_type, z_type contain the number of bytes read for each +void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz) { + const char *p = model; // pointer to the model character + uint32_t v = 0; // index in the payload bytes buffer + char c = pgm_read_byte(p); // cur char + while (c) { + char c1 = pgm_read_byte(p+1); // next char + if (!c1) { break; } // unexpected end of model + if (isXYZ(c) && (c == c1) && (v < payload.len())) { // if char is [x-z] and followed by same char + uint8_t val = payload.get8(v); + switch (c) { + case 'x': + xyz->x = xyz->x | (val << (xyz->x_type * 8)); + xyz->x_type++; + break; + case 'y': + xyz->y = xyz->y | (val << (xyz->y_type * 8)); + xyz->y_type++; + break; + case 'z': + xyz->z = xyz->z | (val << (xyz->z_type * 8)); + xyz->z_type++; + break; + } + } + p += 2; + v++; + c = pgm_read_byte(p); + } +} + +// works on big endiand hex only +// Returns if found: +// - cluster number +// - command number or 0xFF if command is part of the variable part +// - the payload in the form of a HEX string with x/y/z variables + + + +// Parse a cluster specific command, and try to convert into human readable +void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, const SBuffer &payload) { + size_t hex_char_len = payload.len()*2+2; + char *hex_char = (char*) malloc(hex_char_len); + if (!hex_char) { return; } + ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len); + + const __FlashStringHelper* command_name = nullptr; + Z_XYZ_Var xyz; + +//AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> len = %d - %02X%02X%02X"), payload.len(), payload.get8(0), payload.get8(1), payload.get8(2)); + for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) { + const Z_CommandConverter *conv = &Z_Commands[i]; + if (conv->cluster == cluster) { + // cluster match + if ((0xFF == conv->cmd) || (cmd == conv->cmd)) { + // cmd match + if ((direction && (conv->direction & 0x02)) || (!direction && (conv->direction & 0x01))) { + // check if we have a match for params too + // Match if: + // - payload exactly matches conv->param (conv->param may be longer) + // - payload matches conv->param until 'x', 'y' or 'z' + const char * p = conv->param; + //AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++1 param = %s"), p); + bool match = true; + for (uint8_t i = 0; i < payload.len(); i++) { + const char c1 = pgm_read_byte(p); + const char c2 = pgm_read_byte(p+1); + //AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++2 c1 = %c, c2 = %c"), c1, c2); + if ((0x00 == c1) || isXYZ(c1)) { + break; + } + const char * p2 = p; + uint32_t nextbyte = parseHex_P(&p2, 2); + //AddLog_P2(LOG_LEVEL_INFO, PSTR(">>>++3 parseHex_P = %02X"), nextbyte); + if (nextbyte != payload.get8(i)) { + match = false; + break; + } + p += 2; + } + if (match) { + command_name = (const __FlashStringHelper*) conv->tasmota_cmd; + parseXYZ(conv->param, payload, &xyz); + if (0xFF == conv->cmd) { + // shift all values + xyz.z = xyz.y; + xyz.z_type = xyz.y_type; + xyz.y = xyz.x; + xyz.y_type = xyz.x_type; + xyz.x = cmd; + xyz.x_type = 1; // 1 byte + } + break; + } + } + } + } + } + + // always report attribute in raw format + // Format: "0001!06": "00" = "!": "" for commands to devices + // Format: "0004<00": "00" = "<": "" for commands to devices + char attrid_str[12]; + snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd); + json[attrid_str] = hex_char; + free(hex_char); + + if (command_name) { + if (0 == xyz.x_type) { + json[command_name] = true; // no parameter + } else if (0 == xyz.y_type) { + json[command_name] = xyz.x; // 1 parameter + } else { + // multiple answers, create an array + JsonArray &arr = json.createNestedArray(command_name); + arr.add(xyz.x); + arr.add(xyz.y); + if (xyz.z_type) { + arr.add(xyz.z); + } + } + } +} + +// Find the command details by command name +// If not found: +// - returns nullptr +const __FlashStringHelper* zigbeeFindCommand(const char *command, uint16_t *cluster, uint16_t *cmd) { for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) { const Z_CommandConverter *conv = &Z_Commands[i]; if (0 == strcasecmp_P(command, conv->tasmota_cmd)) { - return (const __FlashStringHelper*) conv->zcl_cmd; + *cluster = conv->cluster; + *cmd = conv->cmd; + return (const __FlashStringHelper*) conv->param; } } return nullptr; } -inline bool isXYZ(char c) { - return (c >= 'x') && (c <= 'z'); -} - // take the lower 4 bits and turn it to an hex char inline char hexDigit(uint32_t h) { uint32_t nybble = h & 0x0F; diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 5a3d78e85..d0616a40b 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -33,6 +33,7 @@ const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters) const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address +const uint8_t ZIGBEE_STATUS_DEVICE_IEEE = 35; // Request of device address const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 668f85471..b67add374 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -176,15 +176,24 @@ int32_t Z_ReceivePermitJoinStatus(int32_t res, const class SBuffer &buf) { return -1; } +// Send ZDO_IEEE_ADDR_REQ request to get IEEE long address +void Z_SendIEEEAddrReq(uint16_t shortaddr) { + uint8_t IEEEAddrReq[] = { Z_SREQ | Z_ZDO, ZDO_IEEE_ADDR_REQ, + Z_B0(shortaddr), Z_B1(shortaddr), 0x00, 0x00 }; + + ZigbeeZNPSend(IEEEAddrReq, sizeof(IEEEAddrReq)); +} + // Send ACTIVE_EP_REQ to collect active endpoints for this address void Z_SendActiveEpReq(uint16_t shortaddr) { uint8_t ActiveEpReq[] = { Z_SREQ | Z_ZDO, ZDO_ACTIVE_EP_REQ, Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; - uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, - Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; - ZigbeeZNPSend(ActiveEpReq, sizeof(ActiveEpReq)); + + // uint8_t NodeDescReq[] = { Z_SREQ | Z_ZDO, ZDO_NODE_DESC_REQ, + // Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr) }; + //ZigbeeZNPSend(NodeDescReq, sizeof(NodeDescReq)); Not sure this is useful } @@ -335,6 +344,40 @@ int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) { return -1; } +int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) { + uint8_t status = buf.get8(2); + Z_IEEEAddress ieeeAddr = buf.get64(3); + Z_ShortAddress nwkAddr = buf.get16(11); + // uint8_t startIndex = buf.get8(13); + // uint8_t numAssocDev = buf.get8(14); + + if (0 == status) { // SUCCESS + zigbee_devices.updateDevice(nwkAddr, ieeeAddr); + char hex[20]; + Uint64toHex(ieeeAddr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + "}}"), + ZIGBEE_STATUS_DEVICE_IEEE, hex, nwkAddr + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + // Ping response + const String * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); + if (friendlyName) { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" + ",\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), nwkAddr, friendlyName->c_str()); + } else { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"}}"), nwkAddr); + } + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + } + return -1; +} + int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { Z_ShortAddress srcAddr = buf.get16(2); Z_ShortAddress nwkAddr = buf.get16(4); @@ -451,6 +494,8 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str()); zcl_received.postProcessAttributes(srcaddr, json); + // Add Endpoint + json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint; // Add linkquality json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; @@ -482,6 +527,7 @@ ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585 ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 +ZBM(AREQ_ZDO_IEEE_ADDR_RSP, Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP) // 4581 const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, @@ -491,6 +537,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, { AREQ_ZDO_SIMPLEDESCRSP, &Z_ReceiveSimpleDesc }, + { AREQ_ZDO_IEEE_ADDR_RSP, &Z_ReceiveIEEEAddr }, }; int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 6ea08c4ac..08055ba9e 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -33,19 +33,22 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" + D_CMND_ZIGBEE_PING ; const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // legacy prefix -- deprecated D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" - D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND ; + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" + D_CMND_ZIGBEE_PING ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbZNPSend, &CmndZbPermitJoin, &CmndZbStatus, &CmndZbReset, &CmndZbSend, &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, - &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind + &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, + &CmndZbPing, }; int32_t ZigbeeProcessInput(class SBuffer &buf) { @@ -344,7 +347,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { ToHex_P(msg, len, hex_char, sizeof(hex_char))); } -void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId) { +void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) { SBuffer buf(25+len); buf.add8(Z_SREQ | Z_AF); // 24 buf.add8(AF_DATA_REQUEST); // 01 @@ -357,7 +360,7 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8 buf.add8(0x1E); // 1E radius buf.add8(3 + len); - buf.add8((disableDefResp ? 0x10 : 0x00) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field + buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00)); // Frame Control Field buf.add8(transacId); // Transaction Sequance Number buf.add8(cmdId); if (len > 0) { @@ -367,61 +370,16 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8 ZigbeeZNPSend(buf.getBuffer(), buf.len()); } -inline int8_t hexValue(char c) { - if ((c >= '0') && (c <= '9')) { - return c - '0'; - } - if ((c >= 'A') && (c <= 'F')) { - return 10 + c - 'A'; - } - if ((c >= 'a') && (c <= 'f')) { - return 10 + c - 'a'; - } - return -1; -} - -uint32_t parseHex(const char **data, size_t max_len = 8) { - uint32_t ret = 0; - for (uint32_t i = 0; i < max_len; i++) { - int8_t v = hexValue(**data); - if (v < 0) { break; } // non hex digit, we stop parsing - ret = (ret << 4) | v; - *data += 1; - } - return ret; -} - -void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { - - uint16_t cluster = 0x0000; // 0x0000 is a valid default value - uint8_t cmd = ZCL_READ_ATTRIBUTES; // default command is READ_ATTRIBUTES - bool clusterSpecific = false; - // Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC" - // where AA is the cluster number, BBBB the command number, CCCC... the payload - // First delimiter is '_' for a global command, or '!' for a cluster specific commanc - cluster = parseHex(&data, 4); - - // delimiter - if (('_' == *data) || ('!' == *data)) { - if ('!' == *data) { clusterSpecific = true; } - data++; - } else { - ResponseCmndChar("Wrong delimiter for payload"); - return; - } - // parse cmd number - cmd = parseHex(&data, 2); - - // move to end of payload - // delimiter is optional - if ('/' == *data) { data++; } // skip delimiter - - size_t size = strlen(data); +void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, bool clusterSpecific, + uint16_t cluster, uint8_t cmd, const char *param) { + size_t size = param ? strlen(param) : 0; SBuffer buf((size+2)/2); // actual bytes buffer for data - while (*data) { - uint8_t code = parseHex(&data, 2); - buf.add8(code); + if (param) { + while (*param) { + uint8_t code = parseHex_P(¶m, 2); + buf.add8(code); + } } if (0 == endpoint) { @@ -430,7 +388,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); } AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), - dstAddr, cluster, endpoint, cmd, data); + dstAddr, cluster, endpoint, cmd, param); if (0 == endpoint) { AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); @@ -438,7 +396,7 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { } // everything is good, we can send the command - ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len()); + ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len(), false, zigbee_devices.getNextSeqNumber(dstAddr)); // now set the timer, if any, to read back the state later if (clusterSpecific) { zigbeeSetCommandTimer(dstAddr, cluster, endpoint); @@ -467,8 +425,12 @@ void CmndZbSend(void) { static char delim[] = ", "; // delimiters for parameters uint16_t device = 0xFFFF; // 0xFFFF is broadcast, so considered valid uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint + // Command elements + uint16_t cluster = 0; + uint8_t cmd = 0; String cmd_str = ""; // the actual low-level command, either specified or computed + // parse JSON const JsonVariant &val_device = getCaseInsensitive(json, PSTR("Device")); if (nullptr != &val_device) { device = zigbee_devices.parseDeviceParam(val_device.as()); @@ -496,8 +458,9 @@ void CmndZbSend(void) { String key = it->key; JsonVariant& value = it->value; uint32_t x = 0, y = 0, z = 0; + uint16_t cmd_var; - const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str()); + const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var); if (tasmota_cmd) { cmd_str = tasmota_cmd; } else { @@ -533,9 +496,16 @@ void CmndZbSend(void) { } } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); + if (0xFF == cmd_var) { // if command number is a variable, replace it with x + cmd = x; + x = y; // and shift other variables + y = z; + } else { + cmd = cmd_var; // or simply copy the cmd number + } cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); } else { // we have zero command, pass through until last error for missing command } @@ -546,9 +516,9 @@ void CmndZbSend(void) { // we have an unsupported command type, just ignore it and fallback to missing command } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"), - device, endpoint, cmd_str.c_str()); - zigbeeZCLSendStr(device, endpoint, cmd_str.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%04X!%02X/%s\"}"), + device, endpoint, cluster, cmd, cmd_str.c_str()); + zigbeeZCLSendStr(device, endpoint, true, cluster, cmd, cmd_str.c_str()); } else { Response_P(PSTR("Missing zigbee 'Send'")); return; @@ -614,16 +584,28 @@ void CmndZbBind(void) { // Probe a specific device to get its endpoints and supported clusters void CmndZbProbe(void) { + CmndZbProbeOrPing(true); +} + +void CmndZbProbeOrPing(boolean probe) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } // everything is good, we can send the command - Z_SendActiveEpReq(shortaddr); + Z_SendIEEEAddrReq(shortaddr); + if (probe) { + Z_SendActiveEpReq(shortaddr); + } ResponseCmndDone(); } +// Ping a device, actually a simplified version of ZbProbe +void CmndZbPing(void) { + CmndZbProbeOrPing(false); +} + // Specify, read or erase a Friendly Name void CmndZbName(void) { // Syntax is: @@ -728,8 +710,13 @@ void CmndZbRead(void) { } } + if (0 == endpoint) { // try to compute the endpoint + endpoint = zigbee_devices.findClusterEndpointIn(device, cluster); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); + } + if ((0 != endpoint) && (attrs_len > 0)) { - ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */); + ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device)); ResponseCmndDone(); } else { ResponseCmndChar("Missing parameters"); diff --git a/tasmota/xdrv_27_shutter.ino b/tasmota/xdrv_27_shutter.ino index c353a1dd4..6f4d711d7 100644 --- a/tasmota/xdrv_27_shutter.ino +++ b/tasmota/xdrv_27_shutter.ino @@ -48,7 +48,7 @@ void (* const ShutterCommand[])(void) PROGMEM = { &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay, &CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime}; - const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d}"; + const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d,\"Target\":%d}"; const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}"; #include @@ -70,7 +70,7 @@ struct SHUTTER { int8_t direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down uint8_t mode = 0; // operation mode definition. see enum type above SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE int16_t motordelay[MAX_SHUTTERS]; // initial motorstarttime in 0.05sec. - int16_t pwm_frequency; // frequency of PWN for stepper motors + int16_t pwm_frequency[MAX_SHUTTERS]; // frequency of PWN for stepper motors uint16_t max_pwm_frequency = 1000; // maximum of PWM frequency for openig the shutter. depend on the motor and drivers uint16_t max_close_pwm_frequency[MAX_SHUTTERS];// maximum of PWM frequency for closeing the shutter. depend on the motor and drivers uint8_t skip_relay_change; // avoid overrun at endstops @@ -82,17 +82,18 @@ void ShutterLogPos(uint32_t i) char stemp2[10]; dtostrfd((float)Shutter.time[i] / steps_per_second, 2, stemp2); AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter%d Real %d, Start %d, Stop %d, Dir %d, Delay %d, Rtc %s [s], Freq %d"), - i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i], stemp2, Shutter.pwm_frequency); + i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i], stemp2, Shutter.pwm_frequency[i]); } void ShutterRtc50mS(void) { - for (uint32_t i = 0; i < shutters_present; i++) { + for (uint8_t i = 0; i < shutters_present; i++) { Shutter.time[i]++; if (Shutter.accelerator[i]) { - Shutter.pwm_frequency += Shutter.accelerator[i]; - Shutter.pwm_frequency = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency)); - analogWriteFreq(Shutter.pwm_frequency); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: accelerator i=%d -> %d"),i, Shutter.accelerator[i]); + Shutter.pwm_frequency[i] += Shutter.accelerator[i]; + Shutter.pwm_frequency[i] = tmax(0,tmin(Shutter.direction[i]==1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i],Shutter.pwm_frequency[i])); + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); } } @@ -102,8 +103,6 @@ void ShutterRtc50mS(void) int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index) { - if (0 == percent) return 0; - if (100 == percent) return Shutter.open_max[index]; if (Settings.shutter_set50percent[index] != 50) { return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; } else { @@ -138,8 +137,6 @@ int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index) uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index) { - if (0 >= realpos) return 0; - if (Shutter.open_max[index] <= realpos) return 100; if (Settings.shutter_set50percent[index] != 50) { return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]); } else { @@ -203,8 +200,9 @@ void ShutterInit(void) Shutter.mode = SHT_OFF_ON__OPEN_CLOSE; if ((pin[GPIO_PWM1+i] < 99) && (pin[GPIO_CNTR1+i] < 99)) { Shutter.mode = SHT_OFF_ON__OPEN_CLOSE_STEPPER; - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + Shutter.accelerator[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); } } @@ -256,29 +254,24 @@ void ShutterInit(void) void ShutterReportPosition(bool always) { - uint32_t shutter_moving = 0; Response_P(PSTR("{")); for (uint32_t i = 0; i < shutters_present; i++) { //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d"), i+1,Shutter.real_position[i]); uint32_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i); if (Shutter.direction[i] != 0) { - shutter_moving = 1; + rules_flag.shutter_moving = 1; ShutterLogPos(i); } if (i) { ResponseAppend_P(PSTR(",")); } - ResponseAppend_P(JSON_SHUTTER_POS, i+1, (Settings.shutter_options[i] & 1) ? 100-position : position, Shutter.direction[i]); + ResponseAppend_P(JSON_SHUTTER_POS, i+1, (Settings.shutter_options[i] & 1) ? 100-position : position, Shutter.direction[i], ShutterRealToPercentPosition(Shutter.target_position[i], i)); } ResponseJsonEnd(); - if (always || (1 == shutter_moving)) { + if (always || (rules_flag.shutter_moving)) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_PRFX_SHUTTER)); + XdrvRulesProcess(); } - if (rules_flag.shutter_moving > shutter_moving) { - rules_flag.shutter_moved = 1; - } else { - rules_flag.shutter_moved = 0; - } - rules_flag.shutter_moving = shutter_moving; - //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved); + } void ShutterLimitRealAndTargetPositions(uint32_t i) { @@ -304,22 +297,22 @@ void ShutterUpdatePosition(void) int32_t max_frequency = Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i]; int32_t max_freq_change_per_sec = Shutter.max_pwm_frequency*steps_per_second / (Shutter.motordelay[i]>0 ? Shutter.motordelay[i] : 1); - int32_t min_runtime_ms = Shutter.pwm_frequency*1000 / max_freq_change_per_sec; + int32_t min_runtime_ms = Shutter.pwm_frequency[i]*1000 / max_freq_change_per_sec; int32_t velocity = Shutter.direction[i] == 1 ? 100 : Shutter.close_velocity[i]; - int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency / max_frequency * Shutter.direction[i] ; + int32_t minstopway = min_runtime_ms * velocity / 100 * Shutter.pwm_frequency[i] / max_frequency * Shutter.direction[i] ; int32_t next_possible_stop = Shutter.real_position[i] + minstopway ; - stop_position_delta =200 * Shutter.pwm_frequency/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]); + stop_position_delta =200 * Shutter.pwm_frequency[i]/max_frequency + Shutter.direction[i] * (next_possible_stop - Shutter.target_position[i]); //Shutter.accelerator[i] = tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*11/200); //int32_t act_freq_change = max_freq_change_per_sec/20; AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: time: %d, velocity %d, minstopway %d,cur_freq %d, max_frequency %d, act_freq_change %d, min_runtime_ms %d, act.pos %d, next_stop %d, target: %d"),Shutter.time[i],velocity,minstopway, - Shutter.pwm_frequency,max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]); + Shutter.pwm_frequency[i],max_frequency, Shutter.accelerator[i],min_runtime_ms,Shutter.real_position[i], next_possible_stop,Shutter.target_position[i]); if (Shutter.accelerator[i] < 0 || next_possible_stop * Shutter.direction[i] > Shutter.target_position[i] * Shutter.direction[i] ) { Shutter.accelerator[i] = - tmin(tmax(max_freq_change_per_sec*(100-(Shutter.direction[i]*(Shutter.target_position[i]-next_possible_stop) ))/2000 , max_freq_change_per_sec*9/200), max_freq_change_per_sec*12/200); //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Ramp down: acc: %d"), Shutter.accelerator[i]); - } else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency == max_frequency) { + } else if ( Shutter.accelerator[i] > 0 && Shutter.pwm_frequency[i] == max_frequency) { Shutter.accelerator[i] = 0; } } else { @@ -343,13 +336,13 @@ void ShutterUpdatePosition(void) case SHT_OFF_ON__OPEN_CLOSE_STEPPER: missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i]; //prepare for stop PWM - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency[i]); Shutter.accelerator[i] = 0; - Shutter.pwm_frequency = Shutter.pwm_frequency > 250 ? 250 : Shutter.pwm_frequency; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = Shutter.pwm_frequency[i] > 250 ? 250 : Shutter.pwm_frequency[i]; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) { delay(1); } @@ -391,6 +384,7 @@ void ShutterUpdatePosition(void) Shutter.direction[i] = 0; ShutterReportPosition(true); + rules_flag.shutter_moved = 1; XdrvRulesProcess(); } } @@ -413,8 +407,8 @@ void ShutterStartInit(uint32_t i, int32_t direction, int32_t target_pos) Shutter.skip_relay_change = 1; } else { if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE_STEPPER) { - Shutter.pwm_frequency = 0; - analogWriteFreq(Shutter.pwm_frequency); + Shutter.pwm_frequency[i] = 0; + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 0); // can be operated without counter, but then not that acurate. if (pin[GPIO_CNTR1+i] < 99) { @@ -439,10 +433,11 @@ void ShutterWaitForMotorStop(uint32_t i) if ((SHT_OFF_ON__OPEN_CLOSE == Shutter.mode) || (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode)) { if (SHT_OFF_ON__OPEN_CLOSE_STEPPER == Shutter.mode) { //AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Frequency change %d"), Shutter.pwm_frequency); - while (Shutter.pwm_frequency > 0) { - Shutter.accelerator[i] = 0; - Shutter.pwm_frequency = tmax(Shutter.pwm_frequency-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0); - analogWriteFreq(Shutter.pwm_frequency); + while (Shutter.pwm_frequency[i] > 0) { + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Frequency: %ld, delta: %d"), Shutter.pwm_frequency[i], (int32_t)((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) ); + Shutter.pwm_frequency[i] = tmax(Shutter.pwm_frequency[i]-((Shutter.direction[i] == 1 ? Shutter.max_pwm_frequency : Shutter.max_close_pwm_frequency[i])/(Shutter.motordelay[i]+1)) , 0); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Frequency: %ld"), Shutter.pwm_frequency[i]); + analogWriteFreq(Shutter.pwm_frequency[i]); analogWrite(pin[GPIO_PWM1+i], 50); delay(50); } @@ -532,7 +527,6 @@ void ShutterButtonHandler(void) uint8_t shutter_index = Settings.shutter_button[button_index] & 0x03; uint16_t loops_per_second = 1000 / Settings.button_debounce; // ButtonDebounce (50) - if ((PRESSED == button) && (NOT_PRESSED == Button.last_state[button_index])) { if (Settings.flag.button_single) { // SetOption13 (0) - Allow only single button press for immediate action buttonState = SHT_PRESSED_MULTI; @@ -542,9 +536,9 @@ void ShutterButtonHandler(void) buttonState = SHT_PRESSED_IMMEDIATE; press_index = 1; Button.press_counter[button_index] = 99; // Remember to discard further action for press & hold within button timings - } else + } else { Button.press_counter[button_index] = (Button.window_timer[button_index]) ? Button.press_counter[button_index] +1 : 1; - Button.window_timer[button_index] = loops_per_second / 2; // 0.5 second multi press window + } } blinks = 201; } @@ -596,14 +590,18 @@ void ShutterButtonHandler(void) // check for simultaneous shutter button press uint32 min_shutterbutton_press_counter = -1; for (uint32_t i = 0; i < MAX_KEYS; i++) { - if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.press_counter[i] < min_shutterbutton_press_counter)) + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Settings.shutter_button[i] %ld, shutter_index %d, Button.press_counter[i] %d, min_shutterbutton_press_counter %d"), Settings.shutter_button[i], shutter_index, Button.press_counter[i] , min_shutterbutton_press_counter); + if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index) && (Button.press_counter[i] < min_shutterbutton_press_counter)) { min_shutterbutton_press_counter = Button.press_counter[i]; + } + } if (min_shutterbutton_press_counter == Button.press_counter[button_index]) { // simultaneous shutter button press detected + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:simultanous presss deteced")); press_index = Button.press_counter[button_index]; for (uint32_t i = 0; i < MAX_KEYS; i++) - if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) == shutter_index)) + if ((Settings.shutter_button[i] & (1<<31)) && ((Settings.shutter_button[i] & 0x03) != shutter_index)) Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS; } @@ -628,7 +626,6 @@ void ShutterButtonHandler(void) return; } } else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) { - // simultaneous shutter button extend hold detected if (!Settings.flag.button_restrict) { // no SetOption1 (0) char scmnd[20]; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); @@ -649,8 +646,7 @@ void ShutterButtonHandler(void) if (buttonState == SHT_PRESSED_IMMEDIATE) { XdrvMailbox.payload = XdrvMailbox.index; CmndShutterStop(); - } - else { + } else { uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f; if (position) { if (Shutter.direction[shutter_index]) { @@ -670,12 +666,12 @@ void ShutterButtonHandler(void) Response_P("%d", position); MqttPublish(stopic, false); } - } - } - } - } - } - } + } // for (uint32_t) + } // if (Settings.shutter) + } // ende else + } // if (position) + } // end else + } // if if (Settings.shutter_startrelay[shutter_index] } Response_P(PSTR("{")); ResponseAppend_P(JSON_SHUTTER_BUTTON, shutter_index+1, (buttonState <= SHT_PRESSED_IMMEDIATE) ? (button_index+1) : 0, press_index); @@ -756,6 +752,7 @@ void CmndShutterPosition(void) AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Pos. in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source ); // value 0 with data_len > 0 can mean Open + // special handling fo UP,DOWN,TOGGLE,STOP command comming with payload -99 if ((XdrvMailbox.data_len > 1) && (XdrvMailbox.payload <= 0)) { //UpperCase(XdrvMailbox.data, XdrvMailbox.data); if (!strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_UP) || !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_OPEN) || ((Shutter.direction[index]==0) && !strcasecmp(XdrvMailbox.data,D_CMND_SHUTTER_TOGGLEUP))) { @@ -773,13 +770,13 @@ void CmndShutterPosition(void) } } - int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? 0 : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload); + int8_t target_pos_percent = (XdrvMailbox.payload < 0) ? (XdrvMailbox.payload == -99 ? ShutterRealToPercentPosition(Shutter.real_position[index], index) : 0) : ((XdrvMailbox.payload > 100) ? 100 : XdrvMailbox.payload); // webgui still send also on inverted shutter the native position. target_pos_percent = ((Settings.shutter_options[index] & 1) && (SRC_WEBGUI != last_source)) ? 100 - target_pos_percent : target_pos_percent; if (XdrvMailbox.payload != -99) { //target_pos_percent = (Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent; Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index); - Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1); + //Shutter.accelerator[index] = Shutter.max_pwm_frequency / ((Shutter.motordelay[index] > 0) ? Shutter.motordelay[index] : 1); //Shutter.target_position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, real %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent); } diff --git a/tasmota/xsns_06_dht_v5.ino b/tasmota/xsns_06_dht_v5.ino index f08286b43..9c7003970 100644 --- a/tasmota/xsns_06_dht_v5.ino +++ b/tasmota/xsns_06_dht_v5.ino @@ -100,6 +100,7 @@ bool DhtRead(uint32_t sensor) break; } +/* bool error = false; noInterrupts(); if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) { @@ -127,6 +128,22 @@ bool DhtRead(uint32_t sensor) } interrupts(); if (error) { return false; } +*/ + + uint32_t i = 0; + noInterrupts(); + if (DhtWaitState(sensor, 0) && DhtWaitState(sensor, 1) && DhtWaitState(sensor, 0)) { + for (i = 0; i < 40; i++) { + if (!DhtWaitState(sensor, 1)) { break; } + delayMicroseconds(35); // Was 30 + if (digitalRead(Dht[sensor].pin)) { + dht_data[i / 8] |= (1 << (7 - i % 8)); + } + if (!DhtWaitState(sensor, 0)) { break; } + } + } + interrupts(); + if (i < 40) { return false; } uint8_t checksum = (dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) & 0xFF; if (dht_data[4] != checksum) { diff --git a/tasmota/xsns_35_tx20.ino b/tasmota/xsns_35_tx20.ino index 850c7deda..14fd5c9f9 100644 --- a/tasmota/xsns_35_tx20.ino +++ b/tasmota/xsns_35_tx20.ino @@ -41,7 +41,8 @@ #define XSNS_35 35 #define TX2X_BIT_TIME 1220 // microseconds -#define TX2X_RESET_VALUES 60 // seconds +#define TX2X_WEIGHT_AVG_SAMPLE 150 // seconds +#define TX23_READ_INTERVAL 4 // seconds (don't use less than 3) // The Arduino standard GPIO routines are not enough, // must use some from the Espressif SDK as well @@ -59,8 +60,9 @@ extern "C" { const char HTTP_SNS_TX2X[] PROGMEM = "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_AVG "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MIN "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%s " D_UNIT_KILOMETER_PER_HOUR "{e}" - "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s (%s°){e}"; + "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s %s°{e}"; #endif // USE_WEBSERVER const char kTx2xDirections[] PROGMEM = D_TX20_NORTH "|" @@ -88,11 +90,12 @@ uint16_t tx2x_sc = 0; uint16_t tx2x_sf = 0; float tx2x_wind_speed_kmh = 0; +float tx2x_wind_speed_min = 200.0; float tx2x_wind_speed_max = 0; float tx2x_wind_speed_avg = 0; -float tx2x_wind_sum = 0; -int tx2x_count = 0; uint8_t tx2x_wind_direction = 0; +int tx2x_count = 0; +uint16_t tx2x_avg_samples; bool tx2x_available = false; @@ -121,7 +124,6 @@ void TX2xStartRead(void) * La Crosse TX23 Anemometer datagram after setting TxD to low/high * 1-1 0 1 0-0 11011 0011 111010101111 0101 1100 000101010000 1-1 - Received pin data at 1200 uSec per bit * t s c sa sb sc sd se sf - * 1 0 1-1 00100 1100 000101010000 1010 1100 000101010000 - sa to sd inverted user data, LSB first * t - host pulls TxD low - signals TX23 to sent measurement * s - TxD released - TxD is pulled high due to pullup * c - TX23U pulls TxD low - calculation in progress @@ -152,26 +154,8 @@ void TX2xStartRead(void) for (int32_t bitcount = 41; bitcount > 0; bitcount--) { uint8_t dpin = (digitalRead(pin[GPIO_TX2X_TXD_BLACK])); #ifdef USE_TX23_WIND_SENSOR - if (bitcount > 41 - 5) { - // start - tx2x_sa = (tx2x_sa << 1) | (dpin); - } else if (bitcount > 41 - 5 - 4) { - // wind dir - tx2x_sb = tx2x_sb >> 1 | ((dpin) << 3); - } else if (bitcount > 41 - 5 - 4 - 12) { - // windspeed - tx2x_sc = tx2x_sc >> 1 | ((dpin) << 11); - } else if (bitcount > 41 - 5 - 4 - 12 - 4) { - // checksum - tx2x_sd = tx2x_sd >> 1 | ((dpin) << 3); - } else if (bitcount > 41 - 5 - 4 - 12 - 4 - 4) { - // wind dir (invert) - tx2x_se = tx2x_se >> 1 | ((dpin ^ 1) << 3); - } else { - // windspeed (invert) - tx2x_sf = tx2x_sf >> 1 | ((dpin ^ 1) << 11); - } -#else + dpin ^= 1; +#endif if (bitcount > 41 - 5) { // start frame (invert) tx2x_sa = (tx2x_sa << 1) | (dpin ^ 1); @@ -191,23 +175,14 @@ void TX2xStartRead(void) // windspeed tx2x_sf = tx2x_sf >> 1 | (dpin << 11); } -#endif delayMicroseconds(TX2X_BIT_TIME); } uint8_t chk = (tx2x_sb + (tx2x_sc & 0xf) + ((tx2x_sc >> 4) & 0xf) + ((tx2x_sc >> 8) & 0xf)); chk &= 0xf; -#ifdef USE_TX23_WIND_SENSOR // check checksum, non-inverted with inverted values and max. speed - if ((chk == tx2x_sd) && (tx2x_sb==tx2x_se) && (tx2x_sc==tx2x_sf) && (tx2x_sc < 511)) { - tx2x_available = true; - } -#else - if ((chk == tx2x_sd) && (tx2x_sc < 511)) { // if checksum seems to be ok and wind speed below 51.1 m/s - tx2x_available = true; - } -#endif + tx2x_available = ((chk == tx2x_sd) && (tx2x_sb==tx2x_se) && (tx2x_sc==tx2x_sf) && (tx2x_sc < 511)); #ifdef USE_TX23_WIND_SENSOR } tx23_stage++; @@ -219,49 +194,82 @@ void TX2xStartRead(void) GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pin[GPIO_TX2X_TXD_BLACK]); } -void Tx2xReset(void) +void Tx2xResetStat(void) { + tx2x_wind_speed_min = tx2x_wind_speed_kmh; + tx2x_wind_speed_max = tx2x_wind_speed_kmh; + uint16_t tx2x_prev_avg_samples = tx2x_avg_samples; + if (Settings.tele_period) { + // number for avg samples = teleperiod value if set + tx2x_avg_samples = Settings.tele_period; + } else { + // otherwise use default number of samples for this driver + tx2x_avg_samples = TX2X_WEIGHT_AVG_SAMPLE; + } + if (tx2x_prev_avg_samples != tx2x_avg_samples) { + tx2x_wind_speed_avg = tx2x_wind_speed_kmh; tx2x_count = 0; - tx2x_wind_sum = 0; - tx2x_wind_speed_max = 0; + } } void Tx2xRead(void) { #ifdef USE_TX23_WIND_SENSOR + // TX23 needs to trigger start transmission - TxD Line + // ___________ _ ___ ___ + // |____| |___________| |_| |__XXXXXXXXXX + // trigger start conv Startframe Data + // // note: TX23 speed calculation is unstable when conversion starts // less than 2 seconds after last request - if ((uptime % 3)==0) { + if ((uptime % TX23_READ_INTERVAL)==0) { // TX23 start transmission by pulling down TxD line for at minimum 500ms // so we pull TxD signal to low every 3 seconds tx23_stage = 0; pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT); digitalWrite(pin[GPIO_TX2X_TXD_BLACK], LOW); - } else if ((uptime % 3)==1) { - // after pulling down TxD every 3 second we pull-up TxD every 3+1 seconds - // to trigger start transmission + } else if ((uptime % TX23_READ_INTERVAL)==1) { + // after pulling down TxD: pull-up TxD every x+1 seconds + // to trigger TX23 start transmission tx23_stage = 1; // first rising signal is invalid pinMode(pin[GPIO_TX2X_TXD_BLACK], INPUT_PULLUP); } #endif - if (0==Settings.tele_period && !(uptime % TX2X_RESET_VALUES)) { - Tx2xReset(); + if (0!=Settings.tele_period && Settings.tele_period!=tx2x_avg_samples) { + // new teleperiod value + Tx2xResetStat(); } - else if (tx2x_available) { + if (tx2x_available) { // Wind speed spec: 0 to 180 km/h (0 to 50 m/s) tx2x_wind_speed_kmh = float(tx2x_sc) * 0.36; + if (tx2x_wind_speed_kmh < tx2x_wind_speed_min) { + tx2x_wind_speed_min = tx2x_wind_speed_kmh; + } if (tx2x_wind_speed_kmh > tx2x_wind_speed_max) { tx2x_wind_speed_max = tx2x_wind_speed_kmh; } - tx2x_count++; - tx2x_wind_sum += tx2x_wind_speed_kmh; - tx2x_wind_speed_avg = tx2x_wind_sum / tx2x_count; + // exponentially weighted average is not quite as smooth as the arithmetic average + // but close enough to the moving average and does not require the regular reset + // of the divider with the associated jump in avg values after period is over + if (tx2x_count <= tx2x_avg_samples) { + tx2x_count++; + } + tx2x_wind_speed_avg -= tx2x_wind_speed_avg / tx2x_count; + tx2x_wind_speed_avg += tx2x_wind_speed_kmh / tx2x_count; + tx2x_wind_direction = tx2x_sb; + + if (!(uptime % tx2x_avg_samples)) { + tx2x_wind_speed_min = tx2x_wind_speed_kmh; + tx2x_wind_speed_max = tx2x_wind_speed_kmh; + + } } } void Tx2xInit(void) { + Tx2xResetStat(); #ifdef USE_TX23_WIND_SENSOR tx23_stage = 0; pinMode(pin[GPIO_TX2X_TXD_BLACK], OUTPUT); @@ -276,22 +284,46 @@ void Tx2xShow(bool json) { char wind_speed_string[33]; dtostrfd(tx2x_wind_speed_kmh, 1, wind_speed_string); + char wind_speed_min_string[33]; + dtostrfd(tx2x_wind_speed_min, 1, wind_speed_min_string); char wind_speed_max_string[33]; dtostrfd(tx2x_wind_speed_max, 1, wind_speed_max_string); char wind_speed_avg_string[33]; dtostrfd(tx2x_wind_speed_avg, 1, wind_speed_avg_string); + char wind_direction_degree_string[33]; + dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_degree_string); char wind_direction_string[4]; GetTextIndexed(wind_direction_string, sizeof(wind_direction_string), tx2x_wind_direction, kTx2xDirections); - char wind_direction_degree[33]; - dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_degree); if (json) { +#ifdef USE_TX2x_LEGACY_JSON ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\",\"Degree\":%s}"), wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree); - Tx2xReset(); +#else + // new format grouped by Speed and Dir(ection) + // Card = cardianal (N../O../S../W..) + // Deg = Degree + ResponseAppend_P( + PSTR(",\"" D_TX2x_NAME "\":{\"Speed\":{\"Act\":%s,\"Avg\":%s,\"Min\":%s,\"Max\":%s},\"Direction\":{\"Cardinal\":\"%s\",\"Degree\":%s}}"), + wind_speed_string, + wind_speed_avg_string, + wind_speed_min_string, + wind_speed_max_string, + wind_direction_string, + wind_direction_degree_string + ); +#endif #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TX2X, wind_speed_string, wind_speed_avg_string, wind_speed_max_string, wind_direction_string, wind_direction_degree); + WSContentSend_PD( + HTTP_SNS_TX2X, + wind_speed_string, + wind_speed_avg_string, + wind_speed_min_string, + wind_speed_max_string, + wind_direction_string, + wind_direction_degree_string + ); #endif // USE_WEBSERVER } } @@ -313,11 +345,11 @@ bool Xsns35(uint8_t function) Tx2xRead(); break; case FUNC_JSON_APPEND: - Tx2xShow(1); + Tx2xShow(true); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: - Tx2xShow(0); + Tx2xShow(false); break; #endif // USE_WEBSERVER } diff --git a/tasmota/xsns_61_MI_NRF24.ino b/tasmota/xsns_61_MI_NRF24.ino index 8527c9749..f1db19d74 100644 --- a/tasmota/xsns_61_MI_NRF24.ino +++ b/tasmota/xsns_61_MI_NRF24.ino @@ -21,6 +21,9 @@ Version yyyymmdd Action Description -------------------------------------------------------------------------------------------- + 0.9.3.0 20200222 integrate - use now the correct id-word instead of MAC-OUI, + add CGG1 + --- 0.9.2.0 20200212 integrate - "backports" from MI-HM10, change reading pattern, add missing PDU-types, renaming driver --- @@ -58,18 +61,21 @@ #define MJ_HT_V1 2 #define LYWSD02 3 #define LYWSD03 4 +#define CGG1 5 -uint8_t kMINRFSlaveID[4][3] = { 0xC4,0x7C,0x8D, // Flora - 0x58,0x2D,0x34, // MJ_HT_V1 - 0xE7,0x2E,0x00, // LYWSD02 - 0xA4,0xC1,0x38, // LYWSD03 - }; +const uint16_t kMINRFSlaveID[5]={ 0x0098, // Flora + 0x01aa, // MJ_HT_V1 + 0x045b, // LYWSD02 + 0x055b, // LYWSD03 + 0x0347 // CGG1 + }; const char kMINRFSlaveType1[] PROGMEM = "Flora"; const char kMINRFSlaveType2[] PROGMEM = "MJ_HT_V1"; const char kMINRFSlaveType3[] PROGMEM = "LYWSD02"; const char kMINRFSlaveType4[] PROGMEM = "LYWSD03"; -const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4}; +const char kMINRFSlaveType5[] PROGMEM = "CGG1"; +const char * kMINRFSlaveType[] PROGMEM = {kMINRFSlaveType1,kMINRFSlaveType2,kMINRFSlaveType3,kMINRFSlaveType4,kMINRFSlaveType5}; // PDU's or different channels 37-39 const uint32_t kMINRFFloPDU[3] = {0x3eaa857d,0xef3b8730,0x71da7b46}; @@ -77,10 +83,11 @@ const uint32_t kMINRFMJPDU[3] = {0x4760cd66,0xdbcc0cd3,0x33048df5}; const uint32_t kMINRFL2PDU[3] = {0x3eaa057d,0xef3b0730,0x71da7646}; // 1 and 3 unsure // const uint32_t kMINRFL3PDU[3] = {0x4760dd78,0xdbcc1ccd,0xffffffff}; //encrypted - 58 58 const uint32_t kMINRFL3PDU[3] = {0x4760cb78,0xdbcc0acd,0x33048beb}; //unencrypted - 30 58 +const uint32_t kMINRFCGPDU[3] = {0x4760cd6e,0xdbcc0cdb,0x33048dfd}; // start-LSFR for different channels 37-39 const uint8_t kMINRFlsfrList_A[3] = {0x4b,0x17,0x23}; // Flora, LYWSD02 -const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43}; // MJ_HT_V1, LYWSD03 +const uint8_t kMINRFlsfrList_B[3] = {0x21,0x72,0x43}; // MJ_HT_V1, LYWSD03, CGG1 #pragma pack(1) // important!! @@ -269,7 +276,7 @@ struct { } MINRF; struct mi_sensor_t{ - uint8_t type; //Flora = 1; MJ_HT_V1=2; LYWSD02=3; LYWSD03=4 + uint8_t type; //Flora = 1; MJ_HT_V1=2; LYWSD02=3; LYWSD03=4; ; CGG1=5 uint8_t serial[6]; uint8_t showedUp; float temp; //Flora, MJ_HT_V1, LYWSD0x @@ -362,6 +369,9 @@ bool MINRFreceivePacket(void) case 4: MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "LYWSD03" mode break; + case 5: + MINRFwhiten((uint8_t *)&MINRF.buffer, sizeof(MINRF.buffer), kMINRFlsfrList_B[MINRF.currentChan]); // "CGG1" mode + break; } // DEBUG_SENSOR_LOG(PSTR("MINRF: LSFR:%x"),_lsfr); // if (_lsfr>254) _lsfr=0; @@ -470,6 +480,9 @@ void MINRFchangePacketModeTo(uint8_t _mode) { if(kMINRFL3PDU[_nextchannel]==0xffffffff) break; NRF24radio.openReadingPipe(0,kMINRFL3PDU[_nextchannel]);// 95 fe 58 30 -> LYWSD03 (= no data message) break; + case 5: // special CGG1 packet + NRF24radio.openReadingPipe(0,kMINRFCGPDU[_nextchannel]); // 95 fe 50 30 -> CGG1 + break; } // DEBUG_SENSOR_LOG(PSTR("MINRF: Change Mode to %u"),_mode); MINRF.packetMode = _mode; @@ -479,24 +492,25 @@ void MINRFchangePacketModeTo(uint8_t _mode) { * @brief Return the slot number of a known sensor or return create new sensor slot * * @param _serial BLE address of the sensor - * @param _type Type number of the sensor, 0xff for Auto-type + * @param _type Type number of the sensor * @return uint32_t Known or new slot in the sensors-vector */ -uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint8_t _type){ - if(_type==0xff){ - DEBUG_SENSOR_LOG(PSTR("MINRF: will test MAC-type")); - for (uint32_t i=0;i<4;i++){ - if(memcmp(_serial,kMINRFSlaveID+i,3)==0){ - DEBUG_SENSOR_LOG(PSTR("MINRF: MAC is type %u"), i); - _type = i+1; - } - else { - DEBUG_SENSOR_LOG(PSTR("MINRF: MAC-type is unknown")); - } +uint32_t MINRFgetSensorSlot(uint8_t (&_serial)[6], uint16_t _type){ + + DEBUG_SENSOR_LOG(PSTR("MINRF: will test ID-type: %x"), _type); + bool _success = false; + for (uint32_t i=0;i<5;i++){ + if(_type == kMINRFSlaveID[i]){ + DEBUG_SENSOR_LOG(PSTR("MINRF: ID is type %u"), i); + _type = i+1; + _success = true; + } + else { + DEBUG_SENSOR_LOG(PSTR("MINRF: ID-type is not: %x"),kMINRFSlaveID[i]); } } - if(_type==0xff) return _type; // error - + if(!_success) return 0xff; + DEBUG_SENSOR_LOG(PSTR("MINRF: vector size %u"), MIBLEsensors.size()); for(uint32_t i=0; i6000){ // happens every 6000/20 = 300 seconds @@ -712,15 +766,16 @@ void MINRF_EVERY_50_MSECOND() { // Every 50mseconds else if (MINRF.packetMode == LYWSD03){ MINRFhandleLYWSD03Packet(); } - - // DEBUG_SENSOR_LOG(PSTR("MINRF: Change packet mode every 50 msec")); - if (MINRF.packetMode == LYWSD03){ + else if (MINRF.packetMode == CGG1){ + MINRFhandleCGG1Packet(); + } + if (MINRF.packetMode == CGG1){ MINRFinitBLE(1); // no real ble packets in release mode, otherwise for developing use 0 } else { MINRFinitBLE(++MINRF.packetMode); } - + MINRFhopChannel(); NRF24radio.startListening(); }