diff --git a/Device_Groups.md b/Device_Groups.md
new file mode 100644
index 000000000..4f2e9530e
--- /dev/null
+++ b/Device_Groups.md
@@ -0,0 +1,45 @@
+# Device Groups
+
+The device groups module provides a framework to allow multiple devices to be in a group with values such as power, light color/temperature/brightness, PWM values, sensor values, etc. shared with other devices in the group. For example, with multiple light modules in a device group, the light settings can be changed on one module and the settings will automatically be changed on the other light modules. Dimmer switch modules could be in a device group with light modules and the dimmer switch could control the power, brightness and colors of all the lights in the group. Multiple dimmer switches could be in a device group to form a 3-way/4-way dimmer switch.
+
+UDP multicasts, followed by UDP unicasts if necessary, are used to send updates to all devices so updates are fast. There is no need for an MQTT server but all the devices in a group must be on the same IP network.
+
+To include device groups support in the build, define USE_DEVICE_GROUPS in your user_config_override. This adds 3.5K to the code size. All devices in a group must be running firmware with device group support and have device groups enabled.
+
+To enable device groups, set Option85 to 1.
+
+
+## Device Groups Operation
+
+The device group name is the MQTT group topic set with the GroupTopic command. All devices in the same IP network with the same group topic are in the same group. Some modules may define additional device groups. For example, if Remote Device Mode is enabled, the PWM Dimmer module defines three devices groups.
+
+The items that are sent to the group and the items that are received from the group are selected with the DevGroupShare command. By default all items are sent and received from the group. An example of when the DevGroupShare command would be used is when you have a group of lights that you control with a dimmer switch and home automation software. You want the dimmer switch to be able to control all items. The home automation software controls each light individually. When it controls the whole group, it actually sends command to each light in the group. If you use the home automation software to turn an individual light on or off or change it’s brightness, color or scheme, you do not want the change to be replicated to the other lights. In this case, you would set the incoming and outgoing item masks to 0xffffffff (all items) on the dimmer switch (DevGroupShare 0xffffffff,0xffffffff) and set the incoming item mask to 0xffffffff and outgoing item mask to 0 on all the lights (DevGroupShare 0xffffffff,0).
+
+
+### Commands
+
+
+
+
+ Command
+ |
+ Parameters
+ |
+
+
+ DevGroupShare
+ |
+ ,<out> = set incoming and outgoing shared item mask (default = 0xffffffff,0xffffffff)
+
+1 = Power, 2 = Light brightness, 4 = Light fade/speed, 8 = Light scheme, 16 = Light color, 32 = Minimum brightness
+ |
+
+
+ GroupTopic<x>
+ |
+ 1 = reset device group <x> MQTT group topic to firmware default (MQTT_GRPTOPIC) and restart
+
+ = set device group <x> MQTT group topic (32 chars max) and restart
+ |
+
+
\ No newline at end of file
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index e5e08674c..1c4197a19 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -105,3 +105,5 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- Add another new DHT driver based on ESPEasy. The old driver can still be used using define USE_DHT_OLD. The previous new driver can be used with define USE_DHT_V2 (#7717)
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
+- Add Zigbee enhanced commands decoding, added ``ZbPing``
+- Add commands ``SetOption85 0/1`` and ``DevGroupShare`` supporting UDP Group command using ``GroupTopic`` without MQTT by Paul Diem (#7790)
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..814c1e75c 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_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#6be561617f645f6a2ae82b8211f6af8c43e834cf
+platform = espressif8266@2.3.3
+platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#abdd2bdbb6a5caf31807d82ebd7b447947a9c360
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..fc1538101 100644
--- a/tasmota/CHANGELOG.md
+++ b/tasmota/CHANGELOG.md
@@ -3,8 +3,11 @@
### 8.1.0.9 20200220
- Revert most wifi connectivity changes introduced in 8.1.0.5 (#7746, #7602, #7621)
+- Fix Zigbee auto-increment transaction number (#7757)
- Add initial support for Sensors AHT10 and AHT15 by Martin Wagner (#7596)
- Add support for Wemos Motor Shield V1 by Denis Sborets (#7764)
+- Add Zigbee enhanced commands decoding, added ``ZbPing``
+- Add commands ``SetOption85 0/1`` and ``DevGroupShare`` supporting UDP Group command using ``GroupTopic`` without MQTT by Paul Diem (#7790)
### 8.1.0.8 20200212
diff --git a/tasmota/i18n.h b/tasmota/i18n.h
index 00cda53b4..0821afb6c 100644
--- a/tasmota/i18n.h
+++ b/tasmota/i18n.h
@@ -289,6 +289,7 @@
#define D_CMND_WIFIPOWER "WifiPower"
#define D_CMND_I2CSCAN "I2CScan"
#define D_CMND_I2CDRIVER "I2CDriver"
+#define D_CMND_DEVGROUP_SHARE "DevGroupShare"
#define D_CMND_SERIALSEND "SerialSend"
#define D_CMND_SERIALDELIMITER "SerialDelimiter"
#define D_CMND_BAUDRATE "Baudrate"
@@ -489,12 +490,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/my_user_config.h b/tasmota/my_user_config.h
index fa31b37ef..bee0ccb29 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -416,6 +416,7 @@
// #define EXS_MCU_CMNDS // Add command to send MCU commands (+0k8 code)
//#define USE_HOTPLUG // Add support for sensor HotPlug
//#define USE_KEELOQ
+//#define USE_DEVICE_GROUPS // Add support for device groups (+5k6 code)
// -- Optional light modules ----------------------
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
diff --git a/tasmota/settings.h b/tasmota/settings.h
index 51151afd1..145c3f40b 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;
@@ -132,7 +132,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t spare28 : 1;
uint32_t spare29 : 1;
uint32_t spare30 : 1;
- uint32_t spare31 : 1;
+ uint32_t force_sdk_erase : 1; // bit 31 (v8.1.0.9) - SetOption113 - Force erase of SDK wifi calibrate secore on restart
};
} SysBitfield4;
@@ -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..1563897b0 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" {
@@ -728,6 +745,7 @@ void SettingsErase(uint8_t type)
1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors
2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF)
3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF)
+ 4 = Erase SDK parameter area used for wifi calibration (0x0FCxxx - 0x0FCFFF)
*/
#ifndef FIRMWARE_MINIMAL
@@ -745,8 +763,18 @@ 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);
+ else if (4 == type) {
+// _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; // SDK phy area and Core calibration sector (0x0FC000)
+ _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)
+ }
+*/
+ 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,7 +783,7 @@ 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);
}
diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino
index c749a3be2..ac44b9850 100644
--- a/tasmota/support_command.ino
+++ b/tasmota/support_command.ino
@@ -30,6 +30,9 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
#ifdef USE_I2C
D_CMND_I2CSCAN "|" D_CMND_I2CDRIVER "|"
#endif
+#ifdef USE_DEVICE_GROUPS
+ D_CMND_DEVGROUP_SHARE "|"
+#endif // USE_DEVICE_GROUPS
D_CMND_SENSOR "|" D_CMND_DRIVER;
void (* const TasmotaCommand[])(void) PROGMEM = {
@@ -45,6 +48,9 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
#ifdef USE_I2C
&CmndI2cScan, CmndI2cDriver,
#endif
+#ifdef USE_DEVICE_GROUPS
+ &CmndDevGroupShare,
+#endif // USE_DEVICE_GROUPS
&CmndSensor, &CmndDriver };
const char kWifiConfig[] PROGMEM =
@@ -1664,6 +1670,17 @@ void CmndI2cDriver(void)
}
#endif // USE_I2C
+#ifdef USE_DEVICE_GROUPS
+void CmndDevGroupShare(void)
+{
+ uint32_t parm[2] = { Settings.device_group_share_in, Settings.device_group_share_out };
+ ParseParameters(2, parm);
+ Settings.device_group_share_in = parm[0];
+ Settings.device_group_share_out = parm[1];
+ Response_P(PSTR("{\"" D_CMND_DEVGROUP_SHARE "\":{\"In\":\"%X\",\"Out\":\"%X\"}}"), Settings.device_group_share_in, Settings.device_group_share_out);
+}
+#endif // USE_DEVICE_GROUPS
+
void CmndSensor(void)
{
XsnsCall(FUNC_COMMAND_SENSOR);
diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino
new file mode 100644
index 000000000..c94ea2f57
--- /dev/null
+++ b/tasmota/support_device_groups.ino
@@ -0,0 +1,713 @@
+/*
+ support_device_groups.ino - device groups support for Tasmota
+
+ Copyright (C) 2020 Paul C Diem
+
+ Device group allow multiple devices to be in a group with power, light
+ brightness, fade and speed settings and other module-specific settings
+ kept in sync across all devices in the group.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#ifdef USE_DEVICE_GROUPS
+
+//#define DEVICE_GROUPS_DEBUG
+
+extern bool udp_connected;
+
+struct device_group_member {
+ struct device_group_member * flink;
+ IPAddress ip_address;
+ uint32_t timeout_time;
+ uint16_t received_sequence;
+ uint16_t acked_sequence;
+};
+
+struct device_group {
+ uint32_t next_ack_check_time;
+ uint16_t last_full_status_sequence;
+ uint16_t message_length;
+ uint8_t message_header_length;
+ uint8_t initial_status_requests_remaining;
+ bool local;
+ char group_name[TOPSZ];
+ char message[128];
+ uint8_t group_member_count;
+ struct device_group_member * device_group_members;
+};
+
+struct device_group * device_groups;
+uint16_t outgoing_sequence = 0;
+bool device_groups_initialized = false;
+bool device_groups_initialization_failed = false;
+bool building_status_message = false;
+bool processing_remote_device_message = false;
+bool waiting_for_acks;
+bool udp_was_connected = false;
+
+void DeviceGroupsInit(void)
+{
+ // Initialize the device information for each device group. The group name is the MQTT group topic.
+ device_groups = (struct device_group *)calloc(device_group_count, sizeof(struct device_group));
+ if (device_groups == nullptr) {
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating %u-element device group array"), device_group_count);
+ device_groups_initialization_failed = true;
+ return;
+ }
+
+ for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
+ struct device_group * device_group = &device_groups[device_group_index];
+ strcpy(device_group->group_name, SettingsText((device_group_index == 0 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + device_group_index - 1)));
+ device_group->message_header_length = sprintf_P(device_group->message, PSTR("%s%s HTTP/1.1\n\n"), kDeviceGroupMessage, device_group->group_name);
+ device_group->last_full_status_sequence = -1;
+ }
+
+ device_groups[0].local = true;
+
+ // If both in and out shared items masks are 0, assume they're unitialized and initialize them.
+ if (!Settings.device_group_share_in && !Settings.device_group_share_out) {
+ Settings.device_group_share_in = Settings.device_group_share_out = 0xffffffff;
+ }
+
+ device_groups_initialized = true;
+}
+
+char * IPAddressToString(const IPAddress& ip_address)
+{
+ static char buffer[16];
+ sprintf_P(buffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
+ return buffer;
+}
+
+// Return true if we're configured to share the specified item.
+bool DeviceGroupItemShared(bool incoming, uint8_t item)
+{
+ uint8_t mask = 0;
+ switch (item) {
+ case DGR_ITEM_LIGHT_BRI:
+ mask = DGR_SHARE_LIGHT_BRI;
+ break;
+ case DGR_ITEM_POWER:
+ mask = DGR_SHARE_POWER;
+ break;
+ case DGR_ITEM_LIGHT_SCHEME:
+ mask = DGR_SHARE_LIGHT_SCHEME;
+ break;
+ case DGR_ITEM_LIGHT_FIXED_COLOR:
+ case DGR_ITEM_LIGHT_CHANNELS:
+ mask = DGR_SHARE_LIGHT_COLOR;
+ break;
+ case DGR_ITEM_LIGHT_FADE:
+ case DGR_ITEM_LIGHT_SPEED:
+ mask = DGR_SHARE_LIGHT_FADE;
+ break;
+ case DGR_ITEM_BRI_MIN:
+ mask = DGR_SHARE_BRI_MIN;
+ break;
+ }
+ return (!mask || ((incoming ? Settings.device_group_share_in : Settings.device_group_share_out) & mask));
+}
+
+void SendDeviceGroupPacket(IPAddress ip, char * packet, int len, const char * label)
+{
+ for (int attempt = 1; attempt <= 5; attempt++) {
+ if (PortUdp.beginPacket(ip, 1900)) {
+ PortUdp.write(packet, len);
+ if (PortUdp.endPacket()) return;
+ }
+ delay(10);
+ }
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error sending %s packet"), label);
+}
+
+void _SendDeviceGroupMessage(uint8_t device_group_index, DeviceGroupMessageType message_type, ...)
+{
+ // If device groups are not enabled, ignore this request.
+ if (!Settings.flag4.device_groups_enabled) return;
+
+ // If UDP is not set up, ignore this request.
+ if (!udp_connected) return;
+
+ // If we're currently processing a remote device message, ignore this request.
+ if (processing_remote_device_message) return;
+
+ // Get a pointer to the device information for this device.
+ device_group * device_group = &device_groups[device_group_index];
+
+ // If we're still sending initial status requests, ignore this request.
+ if (device_group->initial_status_requests_remaining) return;
+
+ // A full status request is a request from a remote device for the status of every item we
+ // control. As long as we're building it, we may as well multicast the status update to all
+ // device group members.
+ char * message_ptr = &device_group->message[device_group->message_header_length];
+ if (message_type == DGR_MSGTYP_FULL_STATUS) {
+
+ // Set the flag indicating we're currently building a status message. SendDeviceGroupMessage
+ // will build but not send messages while this flag is set.
+ building_status_message = true;
+
+ // Call the drivers to build the status update.
+ if (!++outgoing_sequence) outgoing_sequence = 1;
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s full status packet"), device_group->group_name);
+#endif // DEVICE_GROUPS_DEBUG
+ device_group->message_length = 0;
+ SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_POWER, power);
+ XdrvMailbox.command_code = DGR_ITEM_STATUS;
+ XdrvCall(FUNC_DEVICE_GROUP_REQUEST);
+ building_status_message = false;
+
+ // If we have nothing to share with the other members, restore the message sequence and return.
+ if (!device_group->message_length) {
+ if (!--outgoing_sequence) outgoing_sequence = -1;
+ return;
+ }
+ device_group->last_full_status_sequence = outgoing_sequence;
+
+ // Set the status update flag in the outgoing message.
+ *(message_ptr + 2) |= DGR_FLAG_FULL_STATUS;
+ }
+
+ else {
+ bool shared;
+ uint8_t item;
+ uint32_t value;
+ uint8_t * value_ptr;
+ va_list ap;
+
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Building device group %s packet"), device_group->group_name);
+#endif // DEVICE_GROUPS_DEBUG
+ uint16_t original_sequence = outgoing_sequence;
+ if (!building_status_message && message_type != DGR_MSGTYP_PARTIAL_UPDATE && !++outgoing_sequence) outgoing_sequence = 1;
+ *message_ptr++ = outgoing_sequence & 0xff;
+ *message_ptr++ = outgoing_sequence >> 8;
+
+ value = 0;
+ if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME)
+ value |= DGR_FLAG_MORE_TO_COME;
+ else if (message_type == DGR_MSGTYP_UPDATE_DIRECT)
+ value |= DGR_FLAG_DIRECT;
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">sequence=%u, flags=%u"), outgoing_sequence, value);
+#endif // DEVICE_GROUPS_DEBUG
+ *message_ptr++ = value & 0xff;
+ *message_ptr++ = value >> 8;
+ char * first_item_ptr = message_ptr;
+
+ // If we're still building this update or all group members haven't acknowledged the previous
+ // update yet, update the message to include these new updates. First we need to rebuild the
+ // previous update message to remove any items and their values that are included in this new
+ // update.
+ if (device_group->message_length) {
+ uint8_t item_array[32];
+ int item_index = 0;
+ int kept_item_count = 0;
+
+ // Build an array of all the items in this new update.
+ va_start(ap, message_type);
+ while ((item = va_arg(ap, int))) {
+ item_array[item_index++] = item;
+ if (item <= DGR_ITEM_MAX_32BIT)
+ va_arg(ap, int);
+ else if (item <= DGR_ITEM_MAX_STRING)
+ va_arg(ap, char *);
+ else {
+ switch (item) {
+ case DGR_ITEM_LIGHT_CHANNELS:
+ va_arg(ap, uint8_t *) ;
+ break;
+ }
+ }
+ }
+ va_end(ap);
+ item_array[item_index] = 0;
+
+ // Rebuild the previous update message, removing any items their values that are included in
+ // this new update.
+ char * previous_message_ptr = message_ptr;
+ while (item = *previous_message_ptr++) {
+
+ // Search for this item in the new update.
+ for (item_index = 0; item_array[item_index]; item_index++) {
+ if (item_array[item_index] == item) break;
+ }
+
+ // If this item was found in the new update skip over it and it's value.
+ if (item_array[item_index]) {
+ if (item <= DGR_ITEM_MAX_32BIT) {
+ previous_message_ptr++;
+ if (item > DGR_ITEM_MAX_8BIT) {
+ previous_message_ptr++;
+ if (item > DGR_ITEM_MAX_16BIT) {
+ previous_message_ptr++;
+ previous_message_ptr++;
+ }
+ }
+ }
+ else if (item <= DGR_ITEM_MAX_STRING)
+ previous_message_ptr += *previous_message_ptr++;
+ else {
+ switch (item) {
+ case DGR_ITEM_LIGHT_CHANNELS:
+ previous_message_ptr += 5;
+ break;
+ }
+ }
+ }
+
+ // If this item was not found in the new udpate, copy it to the new update message.
+ else {
+ *message_ptr++ = item;
+ if (item <= DGR_ITEM_MAX_32BIT) {
+ *message_ptr++ = *previous_message_ptr++;
+ if (item > DGR_ITEM_MAX_8BIT) {
+ *message_ptr++ = *previous_message_ptr++;
+ if (item > DGR_ITEM_MAX_16BIT) {
+ *message_ptr++ = *previous_message_ptr++;
+ *message_ptr++ = *previous_message_ptr++;
+ }
+ }
+ }
+ else if (item <= DGR_ITEM_MAX_STRING) {
+ *message_ptr++ = value = *previous_message_ptr++;
+ memmove(message_ptr, previous_message_ptr, value);
+ previous_message_ptr += value;
+ message_ptr += value;
+ }
+ else {
+ switch (item) {
+ case DGR_ITEM_LIGHT_CHANNELS:
+ memmove(message_ptr, previous_message_ptr, 5);
+ previous_message_ptr += 5;
+ message_ptr += 5;
+ break;
+ }
+ }
+ kept_item_count++;
+ }
+ }
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%u items carried over from previous update"), kept_item_count);
+#endif // DEVICE_GROUPS_DEBUG
+ }
+
+ // Itertate through the passed items adding them and their values to the message.
+ va_start(ap, message_type);
+ while ((item = va_arg(ap, int))) {
+ shared = DeviceGroupItemShared(false, item);
+ if (shared) *message_ptr++ = item;
+ if (item <= DGR_ITEM_MAX_32BIT) {
+ value = va_arg(ap, int);
+ if (shared) {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u"), item, value);
+#endif // DEVICE_GROUPS_DEBUG
+ *message_ptr++ = value & 0xff;
+ if (item > DGR_ITEM_MAX_8BIT) {
+ value >>= 8;
+ *message_ptr++ = value & 0xff;
+ }
+ if (item > DGR_ITEM_MAX_16BIT) {
+ value >>= 8;
+ *message_ptr++ = value & 0xff;
+ *message_ptr++ = value >> 8;
+ }
+ }
+ }
+ else if (item <= DGR_ITEM_MAX_STRING) {
+ value_ptr = va_arg(ap, uint8_t *);
+ if (shared) {
+ value = strlen((const char *)value_ptr);
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%s"), item, value_ptr);
+#endif // DEVICE_GROUPS_DEBUG
+ *message_ptr++ = value;
+ memcpy(message_ptr, value_ptr, value);
+ message_ptr += value;
+ }
+ }
+ else {
+ switch (item) {
+ case DGR_ITEM_LIGHT_CHANNELS:
+ value_ptr = va_arg(ap, uint8_t *);
+ if (shared) {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(">item=%u, value=%u,%u,%u,%u,%u"), item, *value_ptr, *(value_ptr + 1), *(value_ptr + 2), *(value_ptr + 3), *(value_ptr + 4));
+#endif // DEVICE_GROUPS_DEBUG
+ memmove(message_ptr, value_ptr, 5);
+ message_ptr += 5;
+ }
+ break;
+ }
+ }
+ }
+ va_end(ap);
+
+ // If there weren't any items added to the message, restore the outgoing message sequence and
+ // return.
+ if (message_ptr == first_item_ptr) {
+ outgoing_sequence = original_sequence;
+ return;
+ }
+
+ // Add the EOL item code and calculate the message length.
+ *message_ptr++ = 0;
+ device_group->message_length = message_ptr - device_group->message;
+
+ // If there's going to be more items added to this message, return.
+ if (building_status_message || message_type == DGR_MSGTYP_PARTIAL_UPDATE) return;
+ }
+
+ // Multicast the packet.
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via multicast, sequence=%u"), device_group->message_length, device_group->group_name, device_group->message[device_group->message_header_length] | device_group->message[device_group->message_header_length + 1] << 8);
+#endif // DEVICE_GROUPS_DEBUG
+ SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Multicast"));
+ device_group->next_ack_check_time = millis() + 100;
+
+ if (message_type == DGR_MSGTYP_UPDATE_MORE_TO_COME) {
+ for (struct device_group_member * device_group_member = device_group->device_group_members; device_group_member != nullptr; device_group_member = device_group_member->flink) {
+ device_group_member->acked_sequence = outgoing_sequence;
+ }
+ }
+ else {
+ waiting_for_acks = true;
+ }
+}
+
+void ProcessDeviceGroupMessage(char * packet, int packet_length)
+{
+ // Make the group name a null-terminated string.
+ char * message_group_name = packet + sizeof(DEVICE_GROUP_MESSAGE) - 1;
+ char * message_ptr = strchr(message_group_name, ' ');
+ if (message_ptr == nullptr) return;
+ *message_ptr = 0;
+
+ // Search for a device group with the target group name. If one isn't found, return.
+ struct device_group * device_group;
+ uint32_t device_group_index = 0;
+ for (;;) {
+ device_group = &device_groups[device_group_index];
+ if (!strcmp(message_group_name, device_group->group_name)) break;
+ if (++device_group_index >= device_group_count) return;
+ }
+ *message_ptr++ = ' ';
+
+ // Find the group member. If this is a new group member, add it.
+ IPAddress remote_ip = PortUdp.remoteIP();
+ struct device_group_member * * flink = &device_group->device_group_members;
+ struct device_group_member * device_group_member;
+ for (;;) {
+ device_group_member = *flink;
+ if (!device_group_member) {
+ device_group_member = (struct device_group_member *)calloc(1, sizeof(struct device_group_member));
+ if (device_group_member == nullptr) {
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: error allocating device group member block"));
+ return;
+ }
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: adding member %s (%p)"), IPAddressToString(remote_ip), device_group_member);
+#endif // DEVICE_GROUPS_DEBUG
+ device_group_member->ip_address = remote_ip;
+ *flink = device_group_member;
+ break;
+ }
+ else if (device_group_member->ip_address == remote_ip) {
+ break;
+ }
+ flink = &device_group_member->flink;
+ }
+
+ // Find the start of the actual message (after the http header).
+ message_ptr = strstr_P(message_ptr, PSTR("\n\n"));
+ if (message_ptr == nullptr) return;
+ message_ptr += 2;
+
+ bool light_fade;
+ uint16_t flags;
+ uint16_t item;
+ uint16_t message_sequence;
+ int32_t value;
+
+ // Get the message sequence and flags.
+ if (packet_length - (message_ptr - packet) < 4) goto badmsg; // Malformed message - must be at least 16-bit sequence, 16-bit flags left
+ message_sequence = *message_ptr++;
+ message_sequence |= *message_ptr++ << 8;
+ flags = *message_ptr++;
+ flags |= *message_ptr++ << 8;
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Received device group %s packet from %s: sequence=%u, flags=%u"), device_group->group_name, IPAddressToString(remote_ip), message_sequence, flags);
+#endif // DEVICE_GROUPS_DEBUG
+
+ // If this is an ack message, save the message sequence if it's newwer than the last ack we
+ // received from this member.
+ if (flags == DGR_FLAG_ACK) {
+ if (message_sequence > device_group_member->acked_sequence || device_group_member->acked_sequence - message_sequence < 64536) {
+ device_group_member->acked_sequence = message_sequence;
+ }
+ device_group_member->timeout_time = 0;
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("acked_sequence != device_group->last_full_status_sequence) {
+ _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
+ }
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("received_sequence && device_group_member->received_sequence - message_sequence > 64536) {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("received_sequence = message_sequence;
+
+ // Set the flag indicating we're currently processing a remote device message.
+ // SendDeviceGroupMessage will not send messages while this flag is set.
+ processing_remote_device_message = true;
+
+ /*
+ XdrvMailbox
+ bool grpflg
+ bool usridx
+ uint16_t command_code Item code
+ uint32_t index Flags
+ uint32_t data_len String item value length
+ int32_t payload Integer item value
+ char *topic
+ char *data Pointer to non-integer item value
+ char *command nullptr
+ */
+ XdrvMailbox.command = nullptr; // Indicates the source is a device group update
+ XdrvMailbox.index = flags;
+ light_fade = Settings.light_fade;
+ if (flags & (DGR_FLAG_MORE_TO_COME | DGR_FLAG_DIRECT)) Settings.light_fade = false;
+
+ for (;;) {
+ if (packet_length - (message_ptr - packet) < 1) goto badmsg; // Malformed message
+ item = *message_ptr++;
+ if (!item) break; // Done
+
+#ifdef DEVICE_GROUPS_DEBUG
+ switch (item) {
+ case DGR_ITEM_LIGHT_FADE:
+ case DGR_ITEM_LIGHT_SPEED:
+ case DGR_ITEM_LIGHT_BRI:
+ case DGR_ITEM_LIGHT_SCHEME:
+ case DGR_ITEM_LIGHT_FIXED_COLOR:
+ case DGR_ITEM_BRI_MIN:
+ case DGR_ITEM_BRI_PRESET_LOW:
+ case DGR_ITEM_BRI_PRESET_HIGH:
+ case DGR_ITEM_BRI_POWER_ON:
+ case DGR_ITEM_POWER:
+ case DGR_ITEM_LIGHT_CHANNELS:
+ break;
+ default:
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("DGR: ********** invalid item=%u received from device group %s member %s"), item, device_group->group_name, IPAddressToString(remote_ip));
+ }
+#endif // DEVICE_GROUPS_DEBUG
+
+ XdrvMailbox.command_code = item;
+ if (item <= DGR_ITEM_LAST_32BIT) {
+ value = *message_ptr++;
+ if (item > DGR_ITEM_MAX_8BIT) {
+ value |= *message_ptr++ << 8;
+ if (item > DGR_ITEM_MAX_16BIT) {
+ value |= *message_ptr++ << 16;
+ value |= *message_ptr++ << 24;
+ }
+ }
+ else if (item == DGR_ITEM_LIGHT_FADE) {
+ light_fade = value;
+ }
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("- = packet_length - (message_ptr - packet)) goto badmsg; // Malformed message
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("
- message[device_group->message_header_length];
+ if (!++outgoing_sequence) outgoing_sequence = 1;
+ *message_ptr++ = outgoing_sequence & 0xff;
+ *message_ptr++ = outgoing_sequence >> 8;
+ *message_ptr++ = DGR_FLAG_RESET | DGR_FLAG_STATUS_REQUEST;
+ *message_ptr++ = 0;
+ device_group->message_length = message_ptr - device_group->message;
+ device_group->initial_status_requests_remaining = 5;
+ device_group->next_ack_check_time = millis() + 1000;
+ }
+
+ waiting_for_acks = true;
+ }
+
+ if (device_groups_initialization_failed) return;
+
+ if (waiting_for_acks) {
+ uint32_t now = millis();
+ waiting_for_acks = false;
+ for (uint32_t device_group_index = 0; device_group_index < device_group_count; device_group_index++) {
+ device_group * device_group = &device_groups[device_group_index];
+ if (device_group->next_ack_check_time) {
+ if (device_group->next_ack_check_time <= now) {
+ if (device_group->initial_status_requests_remaining) {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending initial status request for group %s"), device_group->group_name);
+#endif // DEVICE_GROUPS_DEBUG
+ if (--device_group->initial_status_requests_remaining) {
+ SendDeviceGroupPacket(IPAddress(239,255,255,250), device_group->message, device_group->message_length, PSTR("Initial"));
+ device_group->message[device_group->message_header_length + 2] = DGR_FLAG_STATUS_REQUEST; // The reset flag is on only for the first packet - turn it off now
+ device_group->next_ack_check_time = now + 200;
+ waiting_for_acks = true;
+ }
+ else {
+ device_group->next_ack_check_time = 0;
+ _SendDeviceGroupMessage(device_group_index, DGR_MSGTYP_FULL_STATUS);
+ }
+ }
+ else {
+ bool acked = true;
+ struct device_group_member ** flink = &device_group->device_group_members;
+ struct device_group_member * device_group_member;
+ while ((device_group_member = *flink)) {
+ if (device_group_member->acked_sequence != outgoing_sequence) {
+
+ if (device_group_member->timeout_time && device_group_member->timeout_time < now) {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: removing member %s (%p)"), IPAddressToString(device_group_member->ip_address), device_group_member);
+#endif // DEVICE_GROUPS_DEBUG
+ *flink = device_group_member->flink;
+ free(device_group_member);
+ }
+ else {
+#ifdef DEVICE_GROUPS_DEBUG
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DGR: sending %u-byte device group %s packet via unicast to %s, sequence %u, last message acked=%u"), device_group->message_length, device_group->group_name, IPAddressToString(device_group_member->ip_address), outgoing_sequence, device_group_member->acked_sequence);
+#endif // DEVICE_GROUPS_DEBUG
+ SendDeviceGroupPacket(device_group_member->ip_address, device_group->message, device_group->message_length, PSTR("Unicast"));
+ if (!device_group_member->timeout_time) device_group_member->timeout_time = now + 15000;
+ acked = false;
+ flink = &device_group_member->flink;
+ }
+ }
+ else {
+ flink = &device_group_member->flink;
+ }
+ }
+ if (acked) {
+ device_group->next_ack_check_time = 0;
+ device_group->message_length = 0;
+ }
+ else {
+ device_group->next_ack_check_time = now + 500;
+ waiting_for_acks = true;
+ }
+ }
+ }
+ else {
+ waiting_for_acks = true;
+ }
+ }
+ }
+ }
+ }
+ else {
+ udp_was_connected = false;
+ }
+}
+
+#endif // USE_DEVICE_GROUPS
diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino
index 1ea7f631d..b4d699cb6 100644
--- a/tasmota/support_tasmota.ino
+++ b/tasmota/support_tasmota.ino
@@ -541,6 +541,9 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
case POWER_TOGGLE:
power ^= mask;
}
+#ifdef USE_DEVICE_GROUPS
+ if (SRC_REMOTE != source && SRC_RETRY != source) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, power);
+#endif // USE_DEVICE_GROUPS
SetDevicePower(power, source);
#ifdef USE_DOMOTICZ
DomoticzUpdatePowerState(device);
diff --git a/tasmota/support_udp.ino b/tasmota/support_udp.ino
index 71fb07490..9ff745364 100644
--- a/tasmota/support_udp.ino
+++ b/tasmota/support_udp.ino
@@ -85,54 +85,62 @@ void PollUdp(void)
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer);
// Simple Service Discovery Protocol (SSDP)
+ if (Settings.flag2.emulation) {
#ifdef USE_SCRIPT_HUE
- if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
+ if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
#else
- if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
+ if (devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) {
#endif
- udp_response_mutex = true;
+ udp_response_mutex = true;
- udp_remote_ip = PortUdp.remoteIP();
- udp_remote_port = PortUdp.remotePort();
+ udp_remote_ip = PortUdp.remoteIP();
+ udp_remote_port = PortUdp.remotePort();
// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"),
// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer);
- uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
+ uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec
- LowerCase(packet_buffer, packet_buffer);
- RemoveSpace(packet_buffer);
+ LowerCase(packet_buffer, packet_buffer);
+ RemoveSpace(packet_buffer);
#ifdef USE_EMULATION_WEMO
- if (EMUL_WEMO == Settings.flag2.emulation) {
- if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
- TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1);
- return;
+ if (EMUL_WEMO == Settings.flag2.emulation) {
+ if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's
+ TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 1);
+ return;
+ }
+ else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus)
+ (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
+ (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
+ TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2);
+ return;
+ }
}
- else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus)
- (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
- (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
- TickerMSearch.attach_ms(response_delay, WemoRespondToMSearch, 2);
- return;
- }
- }
#endif // USE_EMULATION_WEMO
#ifdef USE_EMULATION_HUE
- if (EMUL_HUE == Settings.flag2.emulation) {
- if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
- (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
- (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
- (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
- TickerMSearch.attach_ms(response_delay, HueRespondToMSearch);
- return;
+ if (EMUL_HUE == Settings.flag2.emulation) {
+ if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) ||
+ (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) ||
+ (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) ||
+ (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) {
+ TickerMSearch.attach_ms(response_delay, HueRespondToMSearch);
+ return;
+ }
}
- }
#endif // USE_EMULATION_HUE
- udp_response_mutex = false;
+ udp_response_mutex = false;
+ continue;
+ }
}
+#ifdef USE_DEVICE_GROUPS
+ if (Settings.flag4.device_groups_enabled && !strncmp_P(packet_buffer, kDeviceGroupMessage, sizeof(DEVICE_GROUP_MESSAGE) - 1)) {
+ ProcessDeviceGroupMessage(packet_buffer, len);
+ }
+#endif // USE_DEVICE_GROUPS
}
optimistic_yield(100);
}
diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino
index df509b9d3..8f7de0f96 100644
--- a/tasmota/support_wifi.ino
+++ b/tasmota/support_wifi.ino
@@ -21,6 +21,10 @@
* Wifi
\*********************************************************************************************/
+// Enable only one of two below debug options
+//#define WIFI_RF_MODE_RF_CAL // Set RF_MODE to RF_CAL for restart and deepsleep during user_rf_pre_init
+//#define WIFI_RF_PRE_INIT // Set RF_MODE to RF_CAL for restart, deepsleep and power on during user_rf_pre_init
+
#ifndef WIFI_RSSI_THRESHOLD
#define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network
#endif
@@ -559,7 +563,11 @@ void WifiCheck(uint8_t param)
StopWebserver();
}
#ifdef USE_EMULATION
- if (Settings.flag2.emulation) { UdpConnect(); }
+#ifdef USE_DEVICE_GROUPS
+ if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { UdpConnect(); }
+#else // USE_DEVICE_GROUPS
+ if (Settings.flag2.emulation) { UdpConnect(); }
+#endif // USE_DEVICE_GROUPS
#endif // USE_EMULATION
#endif // USE_WEBSERVER
@@ -605,6 +613,32 @@ void WifiSetOutputPower(void)
WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10);
}
+/*
+ See Esp.h, core_esp8266_phy.cpp and test_overrides.ino
+ RF_DEFAULT = 0, // RF_CAL or not after deep-sleep wake up, depends on init data byte 108.
+ RF_CAL = 1, // RF_CAL after deep-sleep wake up, there will be large current.
+ RF_NO_CAL = 2, // no RF_CAL after deep-sleep wake up, there will only be small current.
+ RF_DISABLED = 4 // disable RF after deep-sleep wake up, just like modem sleep, there will be the smallest current.
+*/
+#ifdef WIFI_RF_MODE_RF_CAL
+#ifndef USE_DEEPSLEEP
+RF_MODE(RF_CAL);
+#endif // USE_DEEPSLEEP
+#endif // WIFI_RF_MODE_RF_CAL
+
+#ifdef WIFI_RF_PRE_INIT
+bool rf_pre_init_flag = false;
+RF_PRE_INIT()
+{
+#ifndef USE_DEEPSLEEP
+ system_deep_sleep_set_option(1); // The option is 1 by default.
+ system_phy_set_rfoption(RF_CAL);
+#endif // USE_DEEPSLEEP
+ system_phy_set_powerup_option(3); // 3: RF initialization will do the whole RF calibration which will take about 200ms; this increases the current consumption.
+ rf_pre_init_flag = true;
+}
+#endif // WIFI_RF_PRE_INIT
+
void WifiConnect(void)
{
WifiSetState(0);
@@ -614,32 +648,47 @@ void WifiConnect(void)
Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2);
Wifi.retry = Wifi.retry_init;
Wifi.counter = 1;
+
+#ifdef WIFI_RF_PRE_INIT
+ if (rf_pre_init_flag) {
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI "Pre-init done"));
+ }
+#endif // WIFI_RF_PRE_INIT
}
-// Enable from 6.0.0a until 6.1.0a - disabled due to possible cause of bad wifi connect on core 2.3.0
-// Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset
-void WifiDisconnect(void)
-{
- // 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
-}
-
-void WifiShutdown(void)
+void WifiShutdown(bool option = false)
{
+ // option = false - Legacy disconnect also used by DeepSleep
+ // option = true - Disconnect with SDK wifi calibrate sector erase
delay(100); // Allow time for message xfer - disabled v6.1.0b
+
+#ifdef USE_EMULATION
+ UdpDisconnect();
+#endif // USE_EMULATION
+
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
MqttDisconnect();
}
- WifiDisconnect();
+
+ if (option && Settings.flag4.force_sdk_erase) { // SetOption113 - Force erase of SDK wifi calibrate sector on restart
+ WiFi.disconnect(false); // Disconnect wifi
+ SettingsErase(4); // Delete SDK wifi config and calibrate data
+ } else {
+ // Enable from 6.0.0a until 6.1.0a - disabled due to possible cause of bad wifi connect on core 2.3.0
+ // Re-enabled from 6.3.0.7 with ESP.restart replaced by ESP.reset
+ // 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.
}
void EspRestart(void)
{
- WifiShutdown();
+ WifiShutdown(true);
CrashDumpClear(); // Clear the stack dump in RTC
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
ESP.reset();
diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h
index 377f9e8b8..5745ff9c6 100644
--- a/tasmota/tasmota.h
+++ b/tasmota/tasmota.h
@@ -277,7 +277,8 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR, FUNC_ANY_KEY,
FUNC_ENERGY_EVERY_SECOND, FUNC_ENERGY_RESET,
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
- FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN};
+ FUNC_WEB_ADD_BUTTON, FUNC_WEB_ADD_MAIN_BUTTON, FUNC_WEB_ADD_HANDLER, FUNC_SET_CHANNELS, FUNC_SET_SCHEME, FUNC_HOTPLUG_SCAN,
+ FUNC_DEVICE_GROUP_REQUEST };
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
@@ -299,8 +300,32 @@ enum SettingsTextIndex { SET_OTAURL,
SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16,
+ SET_MQTT_GRP_TOPIC2, SET_MQTT_GRP_TOPIC3, SET_MQTT_GRP_TOPIC4,
SET_MAX };
+enum DeviceGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYP_REUPDATE };
+
+enum DeviceGroupMessageFlag { DGR_FLAG_RESET = 1, DGR_FLAG_STATUS_REQUEST = 2, DGR_FLAG_FULL_STATUS = 4, DGR_FLAG_ACK = 8, DGR_FLAG_MORE_TO_COME = 16, DGR_FLAG_DIRECT = 32 };
+
+enum DeviceGroupItem { DGR_ITEM_EOL, DGR_ITEM_STATUS,
+ DGR_ITEM_LIGHT_FADE, DGR_ITEM_LIGHT_SPEED, DGR_ITEM_LIGHT_BRI, DGR_ITEM_LIGHT_SCHEME, DGR_ITEM_LIGHT_FIXED_COLOR,
+ DGR_ITEM_BRI_MIN, DGR_ITEM_BRI_PRESET_LOW, DGR_ITEM_BRI_PRESET_HIGH, DGR_ITEM_BRI_POWER_ON,
+ // Add new 8-bit items before this line
+ DGR_ITEM_LAST_8BIT, DGR_ITEM_MAX_8BIT = 63,
+ DGR_ITEM_ACK,
+ DGR_ITEM_ANALOG1, DGR_ITEM_ANALOG2, DGR_ITEM_ANALOG3, DGR_ITEM_ANALOG4, DGR_ITEM_ANALOG5,
+ // Add new 16-bit items before this line
+ DGR_ITEM_LAST_16BIT, DGR_ITEM_MAX_16BIT = 127,
+ DGR_ITEM_POWER,
+ // Add new 32-bit items before this line
+ DGR_ITEM_LAST_32BIT, DGR_ITEM_MAX_32BIT = 191,
+ // Add new string items before this line
+ DGR_ITEM_LAST_STRING, DGR_ITEM_MAX_STRING = 223,
+ DGR_ITEM_LIGHT_CHANNELS };
+
+enum DeviceGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE_LIGHT_FADE = 4, DGR_SHARE_LIGHT_SCHEME = 8,
+ DGR_SHARE_LIGHT_COLOR = 16, DGR_SHARE_BRI_MIN = 32 };
+
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
SRC_MAX };
diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino
index 2ed7976e1..8e9546ed6 100644
--- a/tasmota/tasmota.ino
+++ b/tasmota/tasmota.ino
@@ -329,6 +329,9 @@ void loop(void)
#ifdef ROTARY_V1
RotaryLoop();
#endif
+#ifdef USE_DEVICE_GROUPS
+ DeviceGroupsLoop();
+#endif // USE_DEVICE_GROUPS
BacklogLoop();
if (TimeReached(state_50msecond)) {
diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h
index a2b49e677..448fcc6b2 100644
--- a/tasmota/tasmota_post.h
+++ b/tasmota/tasmota_post.h
@@ -55,6 +55,9 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#ifdef USE_EMULATION_WEMO
#define USE_EMULATION
#endif
+#ifdef USE_DEVICE_GROUPS
+#define USE_EMULATION
+#endif
#ifdef USE_MQTT_TLS
const uint16_t WEB_LOG_SIZE = 2000; // Max number of characters in weblog
@@ -246,6 +249,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem)
#undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common)
#undef USE_EMULATION_WEMO // Disable Belkin WeMo emulation for Alexa (+6k code, +2k mem common)
+#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
#undef DEBUG_THEO // Disable debug code
#undef USE_DEBUG_DRIVER // Disable debug code
#endif // FIRMWARE_KNX_NO_EMULATION
@@ -283,6 +287,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
+#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
#undef USE_ENERGY_SENSOR // Disable energy sensors (-14k code)
#undef USE_PZEM004T // Disable PZEM004T energy sensor
@@ -359,6 +364,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
+#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
// -- Optional light modules ----------------------
//#undef USE_LIGHT // Also disable all Dimmer/Light support
@@ -580,6 +586,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_DEEPSLEEP // Disable support for deepsleep (+1k code)
#undef USE_EXS_DIMMER // Disable support for EX-Store WiFi Dimmer
#undef USE_HOTPLUG // Disable support for HotPlug
+#undef USE_DEVICE_GROUPS // Disable support for device groups (+3k5 code)
// -- Optional light modules ----------------------
#undef USE_LIGHT // Disable support for lights
@@ -687,11 +694,11 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#endif
#ifndef MESSZ
-//#define MESSZ 1040 // Max number of characters in JSON message string (Hass discovery and nice MQTT_MAX_PACKET_SIZE = 1200)
+//#define MESSZ 1040 // Max number of characters in JSON message string (Hass discovery and nice MQTT_MAX_PACKET_SIZE = 1200)
#define MESSZ (MQTT_MAX_PACKET_SIZE -TOPSZ -7) // Max number of characters in JSON message string
#endif
-//#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
+//#include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0)
#ifndef ARDUINO_ESP8266_RELEASE
#define ARDUINO_ESP8266_RELEASE "STAGE"
#endif
@@ -700,6 +707,14 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_MQTT_TLS_CA_CERT
#endif
+#ifdef USE_DEVICE_GROUPS
+#define SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, ...) _SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, __VA_ARGS__, 0)
+#define SendLocalDeviceGroupMessage(REQUEST_TYPE, ...) _SendDeviceGroupMessage(0, REQUEST_TYPE, __VA_ARGS__, 0)
+#define DEVICE_GROUP_MESSAGE "M-TASMOTA_DGR/"
+const char kDeviceGroupMessage[] PROGMEM = DEVICE_GROUP_MESSAGE;
+uint8_t device_group_count = 1;
+#endif // USE_DEVICE_GROUPS
+
#ifdef DEBUG_TASMOTA_CORE
#define DEBUG_CORE_LOG(...) AddLog_Debug(__VA_ARGS__)
#else
diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino
index 29eece8d4..33efee99b 100644
--- a/tasmota/xdrv_01_webserver.ino
+++ b/tasmota/xdrv_01_webserver.ino
@@ -2824,6 +2824,7 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
void CmndEmulation(void)
{
+#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE)
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
#else
@@ -2837,6 +2838,7 @@ void CmndEmulation(void)
Settings.flag2.emulation = XdrvMailbox.payload;
restart_flag = 2;
}
+#endif
ResponseCmndNumber(Settings.flag2.emulation);
}
#endif // USE_EMULATION
@@ -2970,7 +2972,11 @@ bool Xdrv01(uint8_t function)
case FUNC_LOOP:
PollDnsWebserver();
#ifdef USE_EMULATION
+#ifdef USE_DEVICE_GROUPS
+ if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); }
+#else // USE_DEVICE_GROUPS
if (Settings.flag2.emulation) { PollUdp(); }
+#endif // USE_DEVICE_GROUPS
#endif // USE_EMULATION
break;
case FUNC_COMMAND:
diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino
index aadf44f71..b7bd00aa7 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
@@ -871,10 +871,17 @@ void CmndPublish(void)
void CmndGroupTopic(void)
{
+#ifdef USE_DEVICE_GROUPS
+ uint32_t settings_text_index = (XdrvMailbox.index <= 1 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2);
+#endif // USE_DEVICE_GROUPS
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
+#ifdef USE_DEVICE_GROUPS
+ SettingsUpdateText(settings_text_index, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
+#else // USE_DEVICE_GROUPS
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
+#endif // USE_DEVICE_GROUPS
restart_flag = 2;
}
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino
index a45a5499d..baaf2b7a7 100644
--- a/tasmota/xdrv_04_light.ino
+++ b/tasmota/xdrv_04_light.ino
@@ -1767,6 +1767,10 @@ void LightAnimate(void)
Light.update = true;
}
if (Light.update) {
+#ifdef USE_DEVICE_GROUPS
+ if (Light.power) LightSendDeviceGroupStatus();
+#endif // USE_DEVICE_GROUPS
+
uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
Light.update = false;
@@ -2077,6 +2081,86 @@ void calcGammaBulbs(uint16_t cur_col_10[5]) {
}
}
+#ifdef USE_DEVICE_GROUPS
+void LightSendDeviceGroupStatus()
+{
+ uint8_t channels[LST_MAX];
+ light_state.getChannels(channels);
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme, DGR_ITEM_LIGHT_CHANNELS, channels,
+ DGR_ITEM_LIGHT_BRI, (power ? light_state.getBri() : 0));
+}
+
+void LightHandleDeviceGroupRequest()
+{
+ static bool send_state = false;
+ uint32_t value = XdrvMailbox.payload;
+ switch (XdrvMailbox.command_code) {
+ case DGR_ITEM_EOL:
+ LightAnimate();
+ if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) {
+ light_controller.saveSettings();
+ if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
+ MqttPublishTeleState();
+ }
+ send_state = false;
+ }
+ break;
+ case DGR_ITEM_LIGHT_BRI:
+ if (light_state.getBri() != value) {
+ light_controller.changeBri(value);
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_SCHEME:
+ if (Settings.light_scheme != value) {
+ Settings.light_scheme = value;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_CHANNELS:
+ light_controller.changeChannels((uint8_t *)XdrvMailbox.data);
+ send_state = true;
+ break;
+ case DGR_ITEM_LIGHT_FIXED_COLOR:
+ {
+ power_t save_power = Light.power;
+ if (value) {
+ bool save_decimal_text = Settings.flag.decimal_text;
+ char str[16];
+ XdrvMailbox.index = 2;
+ XdrvMailbox.data_len = sprintf_P(str, PSTR("%u"), value);
+ XdrvMailbox.data = str;
+ CmndSupportColor();
+ Settings.flag.decimal_text = save_decimal_text;
+ }
+ else {
+ Light.fixed_color_index = 0;
+ XdrvMailbox.index = 1;
+ XdrvMailbox.payload = light_state.BriToDimmer(light_state.getBri());
+ CmndWhite();
+ }
+ if (Light.power != save_power) {
+ XdrvMailbox.index = save_power;
+ LightSetPower();
+ }
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_SPEED:
+ if (Settings.light_speed != value && value > 0 && value <= 40) {
+ Settings.light_speed = value;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_STATUS:
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade,
+ DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
+ LightSendDeviceGroupStatus();
+ break;
+ }
+}
+#endif // USE_DEVICE_GROUPS
+
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
@@ -2339,6 +2423,9 @@ void CmndScheme(void)
Light.wheel = parm[1];
}
Settings.light_scheme = XdrvMailbox.payload;
+#ifdef USE_DEVICE_GROUPS
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
+#endif // USE_DEVICE_GROUPS
if (LS_WAKEUP == Settings.light_scheme) {
Light.wakeup_active = 3;
}
@@ -2516,6 +2603,9 @@ void CmndFade(void)
Settings.light_fade ^= 1;
break;
}
+#ifdef USE_DEVICE_GROUPS
+ if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade);
+#endif // USE_DEVICE_GROUPS
if (!Settings.light_fade) { Light.fade_running = false; }
ResponseCmndStateText(Settings.light_fade);
}
@@ -2536,6 +2626,9 @@ void CmndSpeed(void)
}
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
Settings.light_speed = XdrvMailbox.payload;
+#ifdef USE_DEVICE_GROUPS
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
+#endif // USE_DEVICE_GROUPS
}
ResponseCmndNumber(Settings.light_speed);
}
@@ -2588,6 +2681,11 @@ bool Xdrv04(uint8_t function)
case FUNC_EVERY_50_MSECOND:
LightAnimate();
break;
+#ifdef USE_DEVICE_GROUPS
+ case FUNC_DEVICE_GROUP_REQUEST:
+ LightHandleDeviceGroupRequest();
+ break;
+#endif // USE_DEVICE_GROUPS
case FUNC_SET_POWER:
LightSetPower();
break;
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