update to newest version

This commit is contained in:
Staars 2020-09-12 19:28:25 +02:00
commit 1b56a97298
44 changed files with 5704 additions and 5147 deletions

View File

@ -18,7 +18,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32
tasmota32-webcam:
@ -35,7 +34,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-webcam
tasmota32-minimal:
@ -52,7 +50,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-minimal
tasmota32-lite:
runs-on: ubuntu-latest
@ -68,7 +65,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-lite
tasmota32-knx:
runs-on: ubuntu-latest
@ -84,7 +80,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-knx
tasmota32-sensors:
@ -101,7 +96,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-sensors
tasmota32-display:
runs-on: ubuntu-latest
@ -117,7 +111,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-display
tasmota32-ir:
runs-on: ubuntu-latest
@ -133,7 +126,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-ir
tasmota32-BG:
runs-on: ubuntu-latest
@ -149,7 +141,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-BG
tasmota32-BR:
@ -166,7 +157,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-BR
tasmota32-CN:
runs-on: ubuntu-latest
@ -182,7 +172,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-CN
tasmota32-CZ:
runs-on: ubuntu-latest
@ -198,7 +187,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-CZ
tasmota32-DE:
runs-on: ubuntu-latest
@ -214,7 +202,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-DE
tasmota32-ES:
@ -231,7 +218,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-ES
tasmota32-FR:
runs-on: ubuntu-latest
@ -247,7 +233,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-FR
tasmota32-GR:
runs-on: ubuntu-latest
@ -263,7 +248,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-GR
tasmota32-HE:
runs-on: ubuntu-latest
@ -279,7 +263,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-HE
tasmota32-HU:
@ -296,7 +279,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-HU
tasmota32-IT:
runs-on: ubuntu-latest
@ -312,7 +294,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-IT
tasmota32-KO:
runs-on: ubuntu-latest
@ -328,7 +309,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-KO
tasmota32-NL:
runs-on: ubuntu-latest
@ -344,7 +324,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-NL
tasmota32-PL:
@ -361,7 +340,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-PL
tasmota32-PT:
runs-on: ubuntu-latest
@ -377,7 +355,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-PT
tasmota32-RO:
runs-on: ubuntu-latest
@ -393,7 +370,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-RO
tasmota32-RU:
runs-on: ubuntu-latest
@ -409,7 +385,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-RU
tasmota32-SE:
@ -426,7 +401,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-SE
tasmota32-SK:
runs-on: ubuntu-latest
@ -442,7 +416,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-SK
tasmota32-TR:
runs-on: ubuntu-latest
@ -458,7 +431,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-TR
tasmota32-TW:
runs-on: ubuntu-latest
@ -474,7 +446,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-TW
tasmota32-UK:
@ -491,5 +462,4 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-UK

View File

@ -751,7 +751,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32
- uses: actions/upload-artifact@v2
with:
@ -775,7 +774,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-minimal
- uses: actions/upload-artifact@v2
with:
@ -799,7 +797,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-lite
- uses: actions/upload-artifact@v2
with:
@ -823,7 +820,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-webcam
- uses: actions/upload-artifact@v2
with:
@ -847,7 +843,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-knx
- uses: actions/upload-artifact@v2
with:
@ -871,7 +866,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-sensors
- uses: actions/upload-artifact@v2
with:
@ -895,7 +889,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-display
- uses: actions/upload-artifact@v2
with:
@ -919,7 +912,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-ir
- uses: actions/upload-artifact@v2
with:
@ -943,7 +935,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-ircustom
- uses: actions/upload-artifact@v2
with:
@ -967,7 +958,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-BG
- uses: actions/upload-artifact@v2
with:
@ -991,7 +981,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-BR
- uses: actions/upload-artifact@v2
with:
@ -1015,7 +1004,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-CN
- uses: actions/upload-artifact@v2
with:
@ -1039,7 +1027,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-CZ
- uses: actions/upload-artifact@v2
with:
@ -1063,7 +1050,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-DE
- uses: actions/upload-artifact@v2
with:
@ -1087,7 +1073,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-ES
- uses: actions/upload-artifact@v2
with:
@ -1111,7 +1096,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-FR
- uses: actions/upload-artifact@v2
with:
@ -1135,7 +1119,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-GR
- uses: actions/upload-artifact@v2
with:
@ -1159,7 +1142,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-HE
- uses: actions/upload-artifact@v2
with:
@ -1183,7 +1165,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-HU
- uses: actions/upload-artifact@v2
with:
@ -1207,7 +1188,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-IT
- uses: actions/upload-artifact@v2
with:
@ -1231,7 +1211,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-KO
- uses: actions/upload-artifact@v2
with:
@ -1255,7 +1234,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-NL
- uses: actions/upload-artifact@v2
with:
@ -1279,7 +1257,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-PL
- uses: actions/upload-artifact@v2
with:
@ -1303,7 +1280,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-PT
- uses: actions/upload-artifact@v2
with:
@ -1327,7 +1303,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-RO
- uses: actions/upload-artifact@v2
with:
@ -1351,7 +1326,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-RU
- uses: actions/upload-artifact@v2
with:
@ -1375,7 +1349,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-SE
- uses: actions/upload-artifact@v2
with:
@ -1399,7 +1372,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-SK
- uses: actions/upload-artifact@v2
with:
@ -1423,7 +1395,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-TR
- uses: actions/upload-artifact@v2
with:
@ -1447,7 +1418,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-TW
- uses: actions/upload-artifact@v2
with:
@ -1471,7 +1441,6 @@ jobs:
platformio update
- name: Run PlatformIO
run: |
mv -f platformio_override_sample.ini platformio_override.ini
platformio run -e tasmota32-UK
- uses: actions/upload-artifact@v2
with:

View File

@ -10,6 +10,7 @@
| USE_MQTT_TLS_CA_CERT | - | - | - | - | - | - | - |
| USE_MQTT_AWS_IOT | - | - | - | - | - | - | - |
| USE_4K_RSA | - | - | - | - | - | - | - |
| USE_TELEGRAM | - | - | - | - | - | - | - |
| USE_KNX | - | - | - | x | - | - | - |
| USE_WEBSERVER | x | x | x | x | x | x | x |
| USE_JAVASCRIPT_ES6 | - | - | - | - | - | - | - |
@ -69,7 +70,9 @@
| USE_DDSU666 | - | - | - | - | x | - | - |
| USE_SOLAX_X1 | - | - | - | - | - | - | - |
| USE_LE01MR | - | - | - | - | - | - | - |
| USE_BL0940 | - | x | x | x | x | - | - |
| USE_TELEINFO | - | - | - | - | - | - | - |
| USE_IEM3000 | - | - | - | - | - | - | - |
| | | | | | | | |
| USE_ADC_VCC | x | x | - | - | - | x | - |
| USE_COUNTER | - | - | x | x | x | - | x |
@ -126,6 +129,7 @@
| USE_VEML6075 | - | - | - | - | - | - | - |
| USE_VEML7700 | - | - | - | - | - | - | - |
| USE_MCP9808 | - | - | - | - | - | - | - |
| USE_HP303B | - | - | - | - | - | - | - |
| | | | | | | | |
| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
| USE_SPI | - | - | - | - | - | - | x |
@ -143,8 +147,9 @@
| USE_GPS | - | - | - | - | - | - | - |
| USE_HM10 | - | - | - | - | x | - | - |
| USE_HRXL | - | - | - | - | x | - | - |
| USE_TASMOTA_SLAVE | - | - | - | - | - | - | - |
| USE_TASMOTA_CLIENT | - | - | - | - | - | - | - |
| USE_OPENTHERM | - | - | - | - | - | - | - |
| USE_TCP_BRIDGE | - | - | - | - | - | - | - | zbbridge
| | | | | | | | |
| USE_NRF24 | - | - | - | - | - | - | - |
| USE_MIBLE | - | - | - | - | - | - | - |
@ -157,6 +162,7 @@
| USE_IR_REMOTE_FULL | - | - | - | - | - | x | - | Enable ALL protocols
| | | | | | | | |
| USE_SR04 | - | - | - | - | x | - | - |
| USE_DYP | - | - | - | - | - | - | - |
| USE_TM1638 | - | - | - | - | x | - | - |
| USE_HX711 | - | - | - | - | x | - | - |
| USE_TX2x_WIND_SENSOR | - | - | - | - | - | - | - |
@ -186,3 +192,5 @@
| USE_MI_ESP32 | - | - | - | - | - | - | - | - |
| USE_WEBCAM | - | - | - | - | - | - | - | x |
| USE_ETHERNET | - | - | - | - | - | - | - | - |
| USE_I2S_AUDIO | - | - | - | - | - | - | - | - |
| USE_TTGO_WATCH | - | - | - | - | - | - | - | - |

View File

@ -80,4 +80,4 @@ Module | LCode | Description
74 Sonoff D1 | x | Sonoff D1 Wifi and RF Dimmer
75 Sonoff ZbBridge | x | Sonoff Zigbee bridge
Over 1400 additional devices are supported using [templates](TEMPLATES.md).
Over 1500 additional devices are supported using [templates](TEMPLATES.md).

View File

@ -21,7 +21,7 @@ While fallback or downgrading is common practice it was never supported due to S
## Supported Core versions
This release will be supported from ESP8266/Arduino library Core version **2.7.2.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
This release will be supported from ESP8266/Arduino library Core version **2.7.4.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
Support of Core versions before 2.7.1 has been removed.
@ -35,7 +35,7 @@ For initial configuration this release supports Webserver based **WifiManager**
## Provided Binary Downloads
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.2.1**.
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.4.1**.
- **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY**
- **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages.
@ -47,27 +47,17 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- **tasmota-zbbridge.bin** = The dedicated Sonoff Zigbee Bridge version.
- **tasmota-minimal.bin** = The Minimal version allows intermediate OTA uploads to support larger versions and does NOT change any persistent parameter. This version **should NOT be used for initial installation**.
Binaries for ESP8266 based devices can be downloaded from http://ota.tasmota.com/tasmota/release. Binaries for ESP32 based devices can be downloaded from http://ota.tasmota.com/tasmota32/release. The base links can be used for OTA upgrades like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin``
[List](MODULES.md) of embedded modules.
[Complete list](BUILDS.md) of available feature and sensors.
## Changelog
### Version 8.4.0.3
### Version 8.5.0.1
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Change references from http://thehackbox.org to http://ota.tasmota.com
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
- Fix ESP32 PWM range
- Fix display power control (#9114)
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add Zigbee better support for IKEA Motion Sensor
- Add command ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
- Add ESP32 Analog input support for GPIO32 to GPIO39
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors
- Add Zigbee web ui for power metering plugs
- Add better config corruption recovery (#9046)
- Add virtual CT for 4 channels lights, emulating a 5th channel
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
- Fix energy total counters (#9263, #9266)
- Fix crash in ``ZbRestore``
- Add new shutter modes (#9244)
- Add ``#define USE_MQTT_AWS_IOT_LIGHT`` for password based AWS IoT authentication

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ import gzip
OUTPUT_DIR = "build_output{}".format(os.path.sep)
def bin_gzip(source, target, env):
variant = str(target[0]).split(os.path.sep)[1]
variant = str(target[0]).split(os.path.sep)[2]
# create string with location and file names based on variant
bin_file = "{}firmware{}{}.bin".format(OUTPUT_DIR, os.path.sep, variant)

View File

@ -5,7 +5,7 @@ import shutil
OUTPUT_DIR = "build_output{}".format(os.path.sep)
def bin_map_copy(source, target, env):
variant = str(target[0]).split(os.path.sep)[1]
variant = str(target[0]).split(os.path.sep)[2]
# check if output directories exist and create if necessary
if not os.path.isdir(OUTPUT_DIR):

View File

@ -7,16 +7,9 @@
; Please visit documentation for the other options and examples
; http://docs.platformio.org/en/stable/projectconf.html
[platformio]
description = Provide ESP8266 based devices with Web, MQTT and OTA firmware
src_dir = tasmota
build_dir = .pioenvs
workspace_dir = .pioenvs
build_cache_dir = .cache
extra_configs = platformio_tasmota_env.ini
platformio_override.ini
; *** Build/upload environment
; *** Tasmota build variant selection
[build_envs]
default_envs =
; *** Uncomment by deleting ";" in the line(s) below to select version(s)
; tasmota
@ -51,10 +44,21 @@ default_envs =
; tasmota-TW
; tasmota-UK
;
; *** Selection for Tasmota ESP32 is done in platformio_tasmota32.ini
;
; *** alternatively can be done in: platformio_override.ini
; *** See example: platformio_override_sample.ini
; *********************************************************************
[platformio]
description = Provide ESP8266 / ESP32 based devices with Web, MQTT and OTA firmware
src_dir = tasmota
build_cache_dir = .cache
extra_configs = platformio_tasmota32.ini
platformio_tasmota_env.ini
platformio_tasmota_env32.ini
platformio_override.ini
default_envs = ${build_envs.default_envs}
[common]
framework = arduino

View File

@ -9,8 +9,7 @@
; http://docs.platformio.org/en/stable/projectconf.html
[platformio]
extra_configs = platformio_tasmota_env32.ini
platformio_tasmota_cenv.ini
extra_configs = platformio_tasmota_cenv.ini
; *** Build/upload environment
default_envs =
@ -167,44 +166,3 @@ build_type = debug
build_unflags = ${esp_defaults.build_unflags}
build_flags = ${esp82xx_defaults.build_flags}
-Wstack-usage=300
; *** Experimental ESP32 Tasmota version ***
; *** expect the unexpected. Many features not working!!! ***
[common32]
platform = espressif32@1.12.4
platform_packages = tool-esptoolpy@1.20800.0
board = esp32dev
board_build.ldscript = esp32_out.ld
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
board_build.flash_mode = ${common.board_build.flash_mode}
board_build.f_flash = ${common.board_build.f_flash}
board_build.f_cpu = ${common.board_build.f_cpu}
build_unflags = ${esp_defaults.build_unflags}
-Wpointer-arith
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_resetmethod = ${common.upload_resetmethod}
upload_speed = 921600
extra_scripts = ${common.extra_scripts}
build_flags = ${esp_defaults.build_flags}
-D CORE_DEBUG_LEVEL=0
-D BUFFER_LENGTH=128
-D MQTT_MAX_PACKET_SIZE=1200
-D uint32=uint32_t
-D uint16=uint16_t
-D uint8=uint8_t
-D sint8_t=int8_t
-D sint32_t=int32_t
-D sint16_t=int16_t
-D memcpy_P=memcpy
-D memcmp_P=memcmp
lib_extra_dirs =
libesp32
lib_ignore =
cc1101

77
platformio_tasmota32.ini Normal file
View File

@ -0,0 +1,77 @@
; *** BETA ESP32 Tasmota version ***
; *** expect the unexpected. Some features not working!!! ***
[platformio]
; *** Tasmota build variant selection
default_envs = ${build_envs.default_envs}
; *** Uncomment by deleting ";" in the line(s) below to select version(s)
; tasmota32
; tasmota32-webcam
; tasmota32-minimal
; tasmota32-lite
; tasmota32-knx
; tasmota32-sensors
; tasmota32-display
; tasmota32-ir
; tasmota32-ircustom
; tasmota32-BG
; tasmota32-BR
; tasmota32-CN
; tasmota32-CZ
; tasmota32-DE
; tasmota32-ES
; tasmota32-FR
; tasmota32-GR
; tasmota32-HE
; tasmota32-HU
; tasmota32-IT
; tasmota32-KO
; tasmota32-NL
; tasmota32-PL
; tasmota32-PT
; tasmota32-RO
; tasmota32-RU
; tasmota32-SE
; tasmota32-SK
; tasmota32-TR
; tasmota32-TW
; tasmota32-UK
[common32]
platform = espressif32@2.0.0
platform_packages = tool-esptoolpy@1.20800.0
board = esp32dev
board_build.ldscript = esp32_out.ld
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
board_build.flash_mode = ${common.board_build.flash_mode}
board_build.f_flash = ${common.board_build.f_flash}
board_build.f_cpu = ${common.board_build.f_cpu}
build_unflags = ${esp_defaults.build_unflags}
-Wpointer-arith
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_resetmethod = ${common.upload_resetmethod}
upload_speed = 921600
extra_scripts = ${common.extra_scripts}
build_flags = ${esp_defaults.build_flags}
-D CORE_DEBUG_LEVEL=0
-D BUFFER_LENGTH=128
-D MQTT_MAX_PACKET_SIZE=1200
-D uint32=uint32_t
-D uint16=uint16_t
-D uint8=uint8_t
-D sint8_t=int8_t
-D sint32_t=int32_t
-D sint16_t=int16_t
-D memcpy_P=memcpy
-D memcmp_P=memcmp
lib_extra_dirs =
libesp32
lib_ignore =
cc1101

View File

@ -1,18 +1,37 @@
## Released
## Unreleased (development)
### 8.5.0.1 20200907
- Fix energy total counters (#9263, #9266)
- Fix crash in ``ZbRestore``
- Add new shutter modes (#9244)
- Add ``#define USE_MQTT_AWS_IOT_LIGHT`` for password based AWS IoT authentication
- Add Zigbee auto-config when pairing
### 8.5.0 20200907
- Release Hannah
### 8.4.0.3 20200823
- Change references from http://thehackbox.org to http://ota.tasmota.com
- Change references from http://thehackbox.org/tasmota/ to http://ota.tasmota.com/tasmota/
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
- Add Zigbee web ui widget for Lights
- Add ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
- Add Zigbee web ui for power metering plugs
- Add experimental support for ESP32 TTGO Watch and I2S Audio by Gerhard Mutz
### 8.4.0.2 20200813
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Remove support for direct upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
- Fix display power control (#9114)
- Add command ``SetOption103 0/1`` to set TLS mode when TLS is selected
- Add command ``SetOption104 1`` to disable all MQTT retained messages
- Add command ``SetOption106 1`` to create a virtual White ColorTemp for RGBW lights
- Add command ``SetOption107 0/1`` to select virtual White as (0) Warm or (1) Cold
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add better config corruption recovery (#9046)
- Add virtual CT for 4 channels lights, emulating a 5th channel
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)

View File

@ -40,8 +40,10 @@ uint32_t *stack_thunk_light_save = NULL; /* Saved A1 while in BearSSL */
uint32_t stack_thunk_light_refcnt = 0;
//#define _stackSize (5600/4)
#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER
#if defined(USE_MQTT_AWS_IOT)
#define _stackSize (5300/4) // using a light version of bearssl we can save 300 bytes
#elif defined(USE_MQTT_TLS_FORCE_EC_CIPHER)
#define _stackSize (4800/4) // no private key, we can reduce a little, max observed 4300
#else
#define _stackSize (3600/4) // using a light version of bearssl we can save 2k
#endif

View File

@ -872,7 +872,11 @@ extern "C" {
#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER
// we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced
br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15);
br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); // TODO
#endif
#ifdef USE_MQTT_AWS_IOT_LIGHT
static const char * alpn_mqtt = "mqtt";
br_ssl_engine_set_protocol_names(&cc->eng, &alpn_mqtt, 1);
#endif
}
}
@ -880,13 +884,6 @@ extern "C" {
// Called by connect() to do the actual SSL setup and handshake.
// Returns if the SSL handshake succeeded.
bool WiFiClientSecure_light::_connectSSL(const char* hostName) {
// #ifdef USE_MQTT_AWS_IOT
// if ((!_chain_P) || (!_sk_ec_P)) {
// setLastError(ERR_MISSING_EC_KEY);
// return false;
// }
// #endif
// Validation context, either full CA validation or checking only fingerprints
#ifdef USE_MQTT_TLS_CA_CERT
br_x509_minimal_context *x509_minimal;

View File

@ -1,440 +0,0 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle time, or an interval measured in clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef ESP8266
#include "core_esp8266_waveform.h"
#include <Arduino.h>
#include "ets_sys.h"
#include <atomic>
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
// Maximum servicing time for any single IRQ
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
// The latency between in-ISR rearming of the timer and the earliest firing
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
// The SDK and hardware take some time to actually get to our NMI code
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
// Waveform generator can create tones, PWM, and servos
typedef struct {
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
int32_t adjDutyCcys; // Temporary correction for next period
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
WaveformMode mode;
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
} Waveform;
namespace {
static struct {
Waveform pins[17]; // State of all possible pins
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
uint32_t(*timer1CB)() = nullptr;
bool timer1Running = false;
uint32_t nextEventCcy;
} waveform;
}
// Interrupt on/off control
static ICACHE_RAM_ATTR void timer1Interrupt();
// Non-speed critical bits
#pragma GCC optimize ("Os")
static void initTimer() {
timer1_disable();
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
waveform.timer1Running = true;
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
}
static void ICACHE_RAM_ATTR deinitTimer() {
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
timer1_disable();
timer1_isr_init();
waveform.timer1Running = false;
}
extern "C" {
// Set a callback. Pass in NULL to stop it
void setTimer1Callback(uint32_t (*fn)()) {
waveform.timer1CB = fn;
std::atomic_thread_fence(std::memory_order_acq_rel);
if (!waveform.timer1Running && fn) {
initTimer();
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
deinitTimer();
}
}
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
return startWaveformClockCycles(pin,
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
}
// Start up a waveform on a pin, or change the current one. Will change to the new
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
uint32_t periodCcys = highCcys + lowCcys;
if (periodCcys < MAXIRQTICKSCCYS) {
if (!highCcys) {
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
else if (!lowCcys) {
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
}
}
// sanity checks, including mixed signed/unsigned arithmetic safety
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
static_cast<int32_t>(periodCcys) <= 0 ||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
return false;
}
Waveform& wave = waveform.pins[pin];
wave.dutyCcys = highCcys;
wave.adjDutyCcys = 0;
wave.periodCcys = periodCcys;
wave.autoPwm = autoPwm;
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (!(waveform.enabled & pinBit)) {
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
wave.nextPeriodCcy = phaseOffsetCcys;
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
wave.mode = WaveformMode::INIT;
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
if (!wave.dutyCcys) {
// If initially at zero duty cycle, force GPIO off
if (pin == 16) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
if (!waveform.timer1Running) {
initTimer();
}
else if (T1V > IRQLATENCYCCYS) {
// Must not interfere if Timer is due shortly
timer1_write(IRQLATENCYCCYS);
}
}
else {
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
std::atomic_thread_fence(std::memory_order_release);
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
if (runTimeCcys) {
wave.mode = WaveformMode::UPDATEEXPIRY;
std::atomic_thread_fence(std::memory_order_release);
waveform.toSetBits = 1UL << pin;
}
}
std::atomic_thread_fence(std::memory_order_acq_rel);
while (waveform.toSetBits) {
delay(0); // Wait for waveform to update
std::atomic_thread_fence(std::memory_order_acquire);
}
return true;
}
// Stops a waveform on a pin
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
// Can't possibly need to stop anything if there is no timer active
if (!waveform.timer1Running) {
return false;
}
// If user sends in a pin >16 but <32, this will always point to a 0 bit
// If they send >=32, then the shift will result in 0 and it will also return false
std::atomic_thread_fence(std::memory_order_acquire);
const uint32_t pinBit = 1UL << pin;
if (waveform.enabled & pinBit) {
waveform.toDisableBits = 1UL << pin;
std::atomic_thread_fence(std::memory_order_release);
// Must not interfere if Timer is due shortly
if (T1V > IRQLATENCYCCYS) {
timer1_write(IRQLATENCYCCYS);
}
while (waveform.toDisableBits) {
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
std::atomic_thread_fence(std::memory_order_acquire);
}
}
if (!waveform.enabled && !waveform.timer1CB) {
deinitTimer();
}
return true;
}
};
// Speed critical bits
#pragma GCC optimize ("O2")
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
if (ISCPUFREQ160MHZ) {
return isCPU2X ? ccys : (ccys >> 1);
}
else {
return isCPU2X ? (ccys << 1) : ccys;
}
}
static ICACHE_RAM_ATTR void timer1Interrupt() {
const uint32_t isrStartCcy = ESP.getCycleCount();
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
const bool isCPU2X = CPU2X & 1;
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
// Handle enable/disable requests from main app.
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
waveform.toDisableBits = 0;
}
if (waveform.toSetBits) {
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
Waveform& wave = waveform.pins[toSetPin];
switch (wave.mode) {
case WaveformMode::INIT:
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy = waveform.nextEventCcy;
}
if (!wave.expiryCcy) {
wave.mode = WaveformMode::INFINITE;
break;
}
// fall through
case WaveformMode::UPDATEEXPIRY:
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
wave.mode = WaveformMode::EXPIRES;
break;
default:
break;
}
waveform.toSetBits = 0;
}
// Exit the loop if the next event, if any, is sufficiently distant.
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
uint32_t busyPins = waveform.enabled;
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
uint32_t now = ESP.getCycleCount();
uint32_t isrNextEventCcy = now;
while (busyPins) {
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
waveform.nextEventCcy = isrNextEventCcy;
break;
}
isrNextEventCcy = waveform.nextEventCcy;
uint32_t loopPins = busyPins;
while (loopPins) {
const int pin = __builtin_ffsl(loopPins) - 1;
const uint32_t pinBit = 1UL << pin;
loopPins ^= pinBit;
Waveform& wave = waveform.pins[pin];
if (clockDrift) {
wave.endDutyCcy += clockDrift;
wave.nextPeriodCcy += clockDrift;
wave.expiryCcy += clockDrift;
}
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
if (WaveformMode::EXPIRES == wave.mode &&
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
// Disable any waveforms that are done
waveform.enabled ^= pinBit;
busyPins ^= pinBit;
}
else {
const int32_t overshootCcys = now - waveNextEventCcy;
if (overshootCcys >= 0) {
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
if (waveform.states & pinBit) {
// active configuration and forward are 100% duty
if (wave.periodCcys == wave.dutyCcys) {
wave.nextPeriodCcy += periodCcys;
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
if (wave.autoPwm) {
wave.adjDutyCcys += overshootCcys;
}
waveform.states ^= pinBit;
if (16 == pin) {
GP16O = 0;
}
else {
GPOC = pinBit;
}
}
waveNextEventCcy = wave.nextPeriodCcy;
}
else {
wave.nextPeriodCcy += periodCcys;
if (!wave.dutyCcys) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
else {
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
if (dutyCcys <= wave.adjDutyCcys) {
dutyCcys >>= 1;
wave.adjDutyCcys -= dutyCcys;
}
else if (wave.adjDutyCcys) {
dutyCcys -= wave.adjDutyCcys;
wave.adjDutyCcys = 0;
}
wave.endDutyCcy = now + dutyCcys;
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
wave.endDutyCcy = wave.nextPeriodCcy;
}
waveform.states |= pinBit;
if (16 == pin) {
GP16O = 1;
}
else {
GPOS = pinBit;
}
}
waveNextEventCcy = wave.endDutyCcy;
}
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
waveNextEventCcy = wave.expiryCcy;
}
}
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
busyPins ^= pinBit;
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
waveform.nextEventCcy = waveNextEventCcy;
}
}
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
isrNextEventCcy = waveNextEventCcy;
}
}
now = ESP.getCycleCount();
}
clockDrift = 0;
}
int32_t callbackCcys = 0;
if (waveform.timer1CB) {
callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X);
}
now = ESP.getCycleCount();
int32_t nextEventCcys = waveform.nextEventCcy - now;
// Account for unknown duration of timer1CB().
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
waveform.nextEventCcy = now + callbackCcys;
nextEventCcys = callbackCcys;
}
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
int32_t deltaIrqCcys = DELTAIRQCCYS;
int32_t irqLatencyCcys = IRQLATENCYCCYS;
if (isCPU2X) {
nextEventCcys >>= 1;
deltaIrqCcys >>= 1;
irqLatencyCcys >>= 1;
}
// Firing timer too soon, the NMI occurs before ISR has returned.
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
nextEventCcys = irqLatencyCcys;
}
else {
nextEventCcys -= deltaIrqCcys;
}
// Register access is fast and edge IRQ was configured before.
T1L = nextEventCcys;
}
#endif // ESP8266

View File

@ -1,93 +0,0 @@
/*
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
Copyright (c) 2020 Dirk O. Kaar.
The core idea is to have a programmable waveform generator with a unique
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
set to 1-shot mode and is always loaded with the time until the next edge
of any live waveforms.
Up to one waveform generator per pin supported.
Each waveform generator is synchronized to the ESP clock cycle counter, not the
timer. This allows for removing interrupt jitter and delay as the counter
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
allowing for smooth transitions.
This replaces older tone(), analogWrite(), and the Servo classes.
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
cycles (which may be 2 CPU clock cycles @ 160MHz).
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef ESP8266
#include <Arduino.h>
#ifndef __ESP8266_WAVEFORM_H
#define __ESP8266_WAVEFORM_H
#ifdef __cplusplus
extern "C" {
#endif
// Start or change a waveform of the specified high and low times on specific pin.
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS,
uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
// full period.
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
// under load, for applications where frequency or duty cycle must not change, leave false.
// Returns true or false on success or failure.
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys,
uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
// Stop a waveform, if any, on the specified pin.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// Add a callback function to be called on *EVERY* timer1 trigger. The
// callback returns the number of microseconds until the next desired call.
// However, since it is called every timer1 interrupt, it may be called
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// generated, stop the timer as well.
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
void setTimer1Callback(uint32_t (*fn)());
#ifdef __cplusplus
}
#endif
#endif
#endif // ESP8266

View File

@ -1,269 +0,0 @@
/*
digital.c - wiring digital implementation for esp8266
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef ESP8266
#define ARDUINO_MAIN
#include "wiring_private.h"
#include "pins_arduino.h"
#include "c_types.h"
#include "eagle_soc.h"
#include "ets_sys.h"
#include "user_interface.h"
#include "core_esp8266_waveform.h"
#include "interrupts.h"
extern "C" {
volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 };
extern void __pinMode(uint8_t pin, uint8_t mode) {
if(pin < 16){
if(mode == SPECIAL){
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
GPEC = (1 << pin); //Disable
GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin)
if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
} else if(mode & FUNCTION_0){
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
GPEC = (1 << pin); //Disable
GPF(pin) = GPFFS((mode >> 4) & 0x07);
if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
} else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD);
GPES = (1 << pin); //Enable
} else if(mode == INPUT || mode == INPUT_PULLUP){
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
GPEC = (1 << pin); //Disable
GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
if(mode == INPUT_PULLUP) {
GPF(pin) |= (1 << GPFPU); // Enable Pullup
}
} else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
GPEC = (1 << pin); //Disable
if(mode == WAKEUP_PULLUP) {
GPF(pin) |= (1 << GPFPU); // Enable Pullup
GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED)
} else {
GPF(pin) |= (1 << GPFPD); // Enable Pulldown
GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED)
}
}
} else if(pin == 16){
GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO
GPC16 = 0;
if(mode == INPUT || mode == INPUT_PULLDOWN_16){
if(mode == INPUT_PULLDOWN_16){
GPF16 |= (1 << GP16FPD);//Enable Pulldown
}
GP16E &= ~1;
} else if(mode == OUTPUT){
GP16E |= 1;
}
}
}
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
stopWaveform(pin);
if(pin < 16){
if(val) GPOS = (1 << pin);
else GPOC = (1 << pin);
} else if(pin == 16){
if(val) GP16O |= 1;
else GP16O &= ~1;
}
}
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
if(pin < 16){
return GPIP(pin);
} else if(pin == 16){
return GP16I & 0x01;
}
return 0;
}
/*
GPIO INTERRUPTS
*/
typedef void (*voidFuncPtr)(void);
typedef void (*voidFuncPtrArg)(void*);
typedef struct {
uint8_t mode;
voidFuncPtr fn;
void * arg;
bool functional;
} interrupt_handler_t;
//duplicate from functionalInterrupt.h keep in sync
typedef struct InterruptInfo {
uint8_t pin;
uint8_t value;
uint32_t micro;
} InterruptInfo;
typedef struct {
InterruptInfo* interruptInfo;
void* functionInfo;
} ArgStructure;
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
static uint32_t interrupt_reg = 0;
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
{
(void) arg;
(void) frame;
uint32_t status = GPIE;
GPIEC = status;//clear them interrupts
uint32_t levels = GPI;
if(status == 0 || interrupt_reg == 0) return;
ETS_GPIO_INTR_DISABLE();
int i = 0;
uint32_t changedbits = status & interrupt_reg;
while(changedbits){
while(!(changedbits & (1 << i))) i++;
changedbits &= ~(1 << i);
interrupt_handler_t *handler = &interrupt_handlers[i];
if (handler->fn &&
(handler->mode == CHANGE ||
(handler->mode & 1) == !!(levels & (1 << i)))) {
// to make ISR compatible to Arduino AVR model where interrupts are disabled
// we disable them before we call the client ISR
esp8266::InterruptLock irqLock; // stop other interrupts
if (handler->functional)
{
ArgStructure* localArg = (ArgStructure*)handler->arg;
if (localArg && localArg->interruptInfo)
{
localArg->interruptInfo->pin = i;
localArg->interruptInfo->value = __digitalRead(i);
localArg->interruptInfo->micro = micros();
}
}
if (handler->arg)
{
((voidFuncPtrArg)handler->fn)(handler->arg);
}
else
{
handler->fn();
}
}
}
ETS_GPIO_INTR_ENABLE();
}
extern void cleanupFunctional(void* arg);
static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional)
{
interrupt_handler_t* handler = &interrupt_handlers[pin];
handler->mode = mode;
handler->fn = userFunc;
if (handler->functional && handler->arg) // Clean when new attach without detach
{
cleanupFunctional(handler->arg);
}
handler->arg = arg;
handler->functional = functional;
}
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode, bool functional)
{
// #5780
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
if ((uint32_t)userFunc >= 0x40200000)
{
// ISR not in IRAM
::printf((PGM_P)F("ISR not in IRAM!\r\n"));
abort();
}
if(pin < 16) {
ETS_GPIO_INTR_DISABLE();
set_interrupt_handlers(pin, (voidFuncPtr)userFunc, arg, mode, functional);
interrupt_reg |= (1 << pin);
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
GPIEC = (1 << pin); //Clear Interrupt for this pin
GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode"
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
ETS_GPIO_INTR_ENABLE();
}
}
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode)
{
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
}
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
if (pin < 16)
{
ETS_GPIO_INTR_DISABLE();
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
GPIEC = (1 << pin); //Clear Interrupt for this pin
interrupt_reg &= ~(1 << pin);
set_interrupt_handlers(pin, nullptr, nullptr, 0, false);
if (interrupt_reg)
{
ETS_GPIO_INTR_ENABLE();
}
}
}
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode)
{
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, 0, mode, false);
}
extern void __resetPins() {
for (int i = 0; i <= 16; ++i) {
if (!isFlashInterfacePin(i))
pinMode(i, INPUT);
}
}
extern void initPins() {
//Disable UART interrupts
system_set_os_print(0);
U0IE = 0;
U1IE = 0;
resetPins();
}
extern void resetPins() __attribute__ ((weak, alias("__resetPins")));
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"), nothrow));
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode) __attribute__((weak, alias("__attachInterruptArg")));
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
};
#endif // ESP8266

View File

@ -1,96 +0,0 @@
/*
pwm.c - analogWrite implementation for esp8266
Use the shared TIMER1 utilities to generate PWM signals
Original Copyright (c) 2015 Hristo Gochkov. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifdef ESP8266
#include <Arduino.h>
#include "core_esp8266_waveform.h"
extern "C" {
static uint32_t analogMap = 0;
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
static uint16_t analogFreq = 1000;
extern void __analogWriteRange(uint32_t range) {
if ((range >= 15) && (range <= 65535)) {
analogScale = range;
}
}
extern void __analogWriteResolution(int res) {
if ((res >= 4) && (res <= 16)) {
analogScale = (1 << res) - 1;
}
}
extern void __analogWriteFreq(uint32_t freq) {
if (freq < 40) {
analogFreq = 40;
} else if (freq > 60000) {
analogFreq = 60000;
} else {
analogFreq = freq;
}
}
extern void __analogWrite(uint8_t pin, int val) {
if (pin > 16) {
return;
}
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
if (val < 0) {
val = 0;
} else if (val > analogScale) {
val = analogScale;
}
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
// val: the duty cycle: between 0 (always off) and 255 (always on).
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
if (analogMap & 1UL << pin) {
analogMap &= ~(1 << pin);
}
else {
pinMode(pin, OUTPUT);
}
uint32_t high = (analogPeriod * val) / analogScale;
uint32_t low = analogPeriod - high;
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
int phaseReference = __builtin_ffs(analogMap) - 1;
if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
analogMap |= (1 << pin);
}
}
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
extern void analogWriteResolution(int res) __attribute__((weak, alias("__analogWriteResolution")));
};
#endif // ESP8266

View File

@ -585,6 +585,7 @@
#define D_CMND_SHUTTER_TOGGLEDIR "ToggleDir"
#define D_CMND_SHUTTER_UP "Up"
#define D_CMND_SHUTTER_DOWN "Down"
#define D_CMND_SHUTTER_MODE "Mode"
#define D_CMND_SHUTTER_STOPOPEN "StopOpen"
#define D_CMND_SHUTTER_STOPCLOSE "StopClose"
#define D_CMND_SHUTTER_STOPTOGGLE "StopToggle"
@ -606,6 +607,7 @@
#define D_CMND_SHUTTER_LOCK "Lock"
#define D_CMND_SHUTTER_ENABLEENDSTOPTIME "EnableEndStopTime"
#define D_CMND_SHUTTER_INVERTWEBBUTTONS "InvertWebButtons"
#define D_CMND_SHUTTER_PWMRANGE "PWMRange"
// Commands xdrv_32_hotplug.ino
#define D_CMND_HOTPLUG "HotPlug"

View File

@ -1,7 +1,7 @@
/*
it-IT.h - localization for Italian - Italy for Tasmota
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 02.09.2020
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 05.09.2020
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
@ -823,8 +823,8 @@
#define D_AS3935_NOISE "rilevato rumore"
#define D_AS3935_DISTDET "rilevato disturbatore"
#define D_AS3935_INTNOEV "Interrupt senza evento!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_FLICKER "Flicker PIN IRQ!"
#define D_AS3935_POWEROFF "Spegnimento"
#define D_AS3935_NOMESS "in ascolto..."
#define D_AS3935_ON "ON"
#define D_AS3935_OFF "OFF"

View File

@ -389,7 +389,8 @@
// #define USE_MQTT_TLS_CA_CERT // Force full CA validation instead of fingerprints, slower, but simpler to use. (+2.2k code, +1.9k mem during connection handshake)
// This includes the LetsEncrypt CA in tasmota_ca.ino for verifying server certificates
// #define USE_MQTT_TLS_FORCE_EC_CIPHER // Force Elliptic Curve cipher (higher security) required by some servers (automatically enabled with USE_MQTT_AWS_IOT) (+11.4k code, +0.4k mem)
// #define USE_MQTT_AWS_IOT // Enable MQTT for AWS IoT - requires a private key (+11.9k code, +0.4k mem)
// #define USE_MQTT_AWS_IOT_LIGHT // Enable MQTT for AWS IoT in light mode, with user/password instead of private certificate
// #define USE_MQTT_AWS_IOT // [Deprecated] Enable MQTT for AWS IoT - requires a private key (+11.9k code, +0.4k mem)
// Note: you need to generate a private key + certificate per device and update 'tasmota/tasmota_aws_iot.cpp'
// Full documentation here: https://github.com/arendst/Tasmota/wiki/AWS-IoT
// #define USE_4K_RSA // Support 4096 bits certificates, instead of 2048
@ -821,7 +822,7 @@
#include "user_config_override.h" // Configuration overrides for my_user_config.h
#endif
#if defined(USE_DISCOVERY) && defined(USE_MQTT_AWS_IOT)
#if defined(USE_DISCOVERY) && (defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_AWS_IOT_LIGHT))
#error "Select either USE_DISCOVERY or USE_MQTT_AWS_IOT, mDNS takes too much code space and is not needed for AWS IoT"
#endif

View File

@ -129,7 +129,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t virtual_ct_cw : 1; // bit 25 (v8.4.0.1) - SetOption107 - Virtual CT Channel - signals whether the hardware white is cold CW (true) or warm WW (false)
uint32_t teleinfo_rawdata : 1; // bit 26 (v8.4.0.2) - SetOption108 - enable Teleinfo + Tasmota Energy device (0) or Teleinfo raw data only (1)
uint32_t alexa_gen_1 : 1; // bit 27 (v8.4.0.3) - SetOption109 - Alexa gen1 mode - if you only have Echo Dot 2nd gen devices
uint32_t spare28 : 1; // bit 28
uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - disable Zigbee auto-config when pairing new devices
uint32_t spare29 : 1; // bit 29
uint32_t spare30 : 1; // bit 30
uint32_t spare31 : 1; // bit 31
@ -609,12 +609,11 @@ struct {
uint8_t ledpwm_off; // F40
uint8_t tcp_baudrate; // F41
uint8_t fallback_module; // F42
uint8_t free_f43[1]; // F43
uint8_t shutter_mode; // F43
uint16_t energy_power_delta[3]; // F44
uint16_t shutter_pwmrange[2][MAX_SHUTTERS]; // F4A
uint8_t free_f4e[106]; // F4A - Decrement if adding new Setting variables just above and below
uint8_t free_f5a[90]; // F5A - Decrement if adding new Setting variables just above and below
// Only 32 bit boundary variables below
SysBitfield5 flag5; // FB4

View File

@ -583,11 +583,12 @@ void CmndStatus(void)
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("{\"" D_STATUS13_SHUTTER "%d\":{\"Relay1\":%d,\"Relay2\":%d,\"Open\":%d,\"Close\":%d,"
"\"50perc\":%d,\"Delay\":%d,\"Opt\":\"%s\","
"\"Calib\":\"%d:%d:%d:%d:%d\"}"),
"\"Calib\":\"%d:%d:%d:%d:%d\","
"\"Mode\":\"%d\"}"),
i, Settings.shutter_startrelay[i], Settings.shutter_startrelay[i] +1, Settings.shutter_opentime[i], Settings.shutter_closetime[i],
Settings.shutter_set50percent[i], Settings.shutter_motordelay[i], GetBinary(&Settings.shutter_options[i], 4).c_str(),
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i]);
}
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i],
Settings.shutter_mode); }
ResponseJsonEnd();
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "13"));
}

View File

@ -598,7 +598,9 @@ void GetFeatures(void)
#ifdef USE_DYP
feature6 |= 0x00400000; // xsns_76_dyp.ino
#endif
// feature6 |= 0x00800000;
#ifdef USE_I2S_AUDIO
feature6 |= 0x00800000; // xdrv_42_i2s_audio.ino
#endif
// feature6 |= 0x01000000;
// feature6 |= 0x02000000;
@ -606,7 +608,9 @@ void GetFeatures(void)
// feature6 |= 0x08000000;
// feature6 |= 0x10000000;
// feature6 |= 0x20000000;
#if defined(ESP32) && defined(USE_TTGO_WATCH)
feature6 |= 0x20000000; // xdrv_83_esp32watch.ino
#endif
#if defined(ESP32) && defined(USE_ETHERNET)
feature6 |= 0x40000000; // xdrv_82_ethernet.ino
#endif

View File

@ -0,0 +1,186 @@
/*
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
Copyright (C) 2020 Theo Arends and Stephan Hadinger
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 <http://www.gnu.org/licenses/>.
*/
/*********************************************************************************************\
*
* private class for Linked List element
*
\*********************************************************************************************/
template <typename T>
class LList;
template <typename T>
class LList_elt {
public:
LList_elt() : _next(nullptr), _val() {}
inline T & val(void) { return _val; }
inline LList_elt<T> * next(void) { return _next; }
inline void next(LList_elt<T> * next) { _next = next; }
friend class LList<T>;
protected:
LList_elt<T> * _next;
T _val;
};
/*********************************************************************************************\
*
* Lightweight Linked List - optimized for low code size
*
\*********************************************************************************************/
template <typename T>
class LList {
public:
LList() : _head(nullptr) {}
~LList() { reset(); }
// remove elements
void removeHead(void); // remove first element
void reset(void); // remove all elements
void remove(const T * val);
// read the list
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
size_t length(void) const;
inline T * head(void) { return _head ? &_head->_val : nullptr; }
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
const T * at(size_t index) const ;
// non-const variants
// not very academic cast but reduces code size
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
// adding elements
T & addHead(void);
T & addHead(const T &val);
T & addToLast(void);
// iterator
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
class iterator {
public:
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const iterator & other) const { return cur != other.cur; }
T & operator*() const { return cur->_val; }
private:
LList_elt<T> *cur;
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
iterator begin() { return iterator(this->_head); } // start with 'head'
iterator end() { return iterator(nullptr); } // end with null pointer
// const iterator
class const_iterator {
public:
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const_iterator & other) const { return cur != other.cur; }
const T & operator*() const { return cur->_val; }
private:
const LList_elt<T> *cur;
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
protected:
LList_elt<T> * _head;
};
template <typename T>
size_t LList<T>::length(void) const {
size_t count = 0;
for (auto & elt : *this) {count++; }
return count;
}
template <typename T>
const T * LList<T>::at(size_t index) const {
size_t count = 0;
for (const auto & elt : *this) {
if (index == count++) { return &elt; }
}
return nullptr;
}
template <typename T>
void LList<T>::reset(void) {
while (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::removeHead(void) {
if (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::remove(const T * val) {
if (nullptr == val) { return; }
// find element in chain and find pointer before
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
LList_elt<T> * curr_elt = *curr_ptr;
if ( &(curr_elt->_val) == val) {
*curr_ptr = curr_elt->_next; // update previous pointer to next element
delete curr_elt;
break; // stop iteration now
}
curr_ptr = &((*curr_ptr)->_next); // move to next element
}
}
template <typename T>
T & LList<T>::addHead(void) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addHead(const T &val) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
elt->_val = val;
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addToLast(void) {
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
curr_ptr = &((*curr_ptr)->_next);
}
LList_elt<T> * elt = new LList_elt<T>(); // create element
*curr_ptr = elt;
return elt->_val;
}

View File

@ -237,3 +237,18 @@ public:
_buf = nullptr;
}
} PreAllocatedSBuffer;
// nullptr accepted
bool equalsSBuffer(const class SBuffer * buf1, const class SBuffer * buf2) {
if (buf1 == buf2) { return true; }
if (!buf1 && (buf2->len() == 0)) { return true; }
if (!buf2 && (buf1->len() == 0)) { return true; }
if (!buf1 || !buf2) { return false; } // at least one buf is not empty
// we know that both buf1 and buf2 are non-null
if (buf1->len() != buf2->len()) { return false; }
size_t len = buf1->len();
for (uint32_t i=0; i<len; i++) {
if (buf1->get8(i) != buf2->get8(i)) { return false; }
}
return true;
}

View File

@ -20,7 +20,7 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x08040003;
const uint32_t VERSION = 0x08050001;
// Lowest compatible version
const uint32_t VERSION_COMPATIBLE = 0x07010006;

View File

@ -154,7 +154,7 @@ void MqttInit(void)
String host = String(SettingsText(SET_MQTT_HOST));
if (host.indexOf(".iot.") && host.endsWith(".amazonaws.com")) { // look for ".iot." and ".amazonaws.com" in the domain name
Settings.flag4.mqtt_no_retain = true;
Mqtt.tls_private_key = true;
// Mqtt.tls_private_key = true;
}
if (Settings.flag4.mqtt_tls) {
@ -353,7 +353,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain
GetTopic_P(stopic, prefix, mqtt_topic, romram);
MqttPublish(stopic, retained);
#ifdef USE_MQTT_AWS_IOT
#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_AWS_IOT_LIGHT)
if ((prefix > 0) && (Settings.flag4.awsiot_shadow) && (Mqtt.connected)) { // placeholder for SetOptionXX
// compute the target topic
char *topic = SettingsText(SET_MQTT_TOPIC);
@ -1350,7 +1350,7 @@ void MqttSaveSettings(void)
#endif
WebGetArg("mc", tmp, sizeof(tmp));
SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp);
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
#if defined(USE_MQTT_TLS) && (defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_AWS_IOT_LIGHT))
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
#else // USE_MQTT_AWS_IOT

File diff suppressed because it is too large Load Diff

View File

@ -849,7 +849,7 @@ void HAssDiscovery(void)
Settings.flag3.hass_tele_on_power = 1; // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT - send tele/STATE message as stat/RESULT
// the purpose of that is so that if HA is restarted, state in HA will be correct within one teleperiod otherwise state
// will not be correct until the device state is changed this is why in the patterns for switch and light, we tell HA to trigger on STATE, not RESULT.
Settings.light_scheme = 0; // To just control color it needs to be Scheme 0
//Settings.light_scheme = 0; // To just control color it needs to be Scheme 0 (on hold due to new light configuration)
}
if (Settings.flag.hass_discovery || (1 == hass_mode))
@ -940,7 +940,7 @@ void HassLwtSubscribe(bool hasslwt)
{
char htopic[TOPSZ];
snprintf_P(htopic, sizeof(htopic), PSTR(HOME_ASSISTANT_LWT_TOPIC));
if (hasslwt) {
if (hasslwt && Settings.flag.hass_discovery) {
MqttSubscribe(htopic);
} else { MqttUnsubscribe(htopic); }
}
@ -984,9 +984,7 @@ bool Xdrv12(uint8_t function)
hass_mode = 0; // Discovery only if Settings.flag.hass_discovery is set
hass_init_step = 2; // Delayed discovery
break;
// if (!Settings.flag.hass_discovery) {
// AddLog_P2(LOG_LEVEL_INFO, PSTR("MQT: homeassistant/49A3BC/Discovery = {\"dev\":{\"ids\":[\"49A3BC\"]},\"cmd_t\":\"cmnd/test1/\",\"Discovery\":0}"));
// }
case FUNC_MQTT_SUBSCRIBE:
HassLwtSubscribe(hasslwt);
break;

View File

@ -506,7 +506,7 @@ void DisplayText(void)
}
}
break;
#endif
#endif // USE_SCRIPT_FATFS
case 'h':
// hor line to
var = atoiv(cp, &temp);
@ -696,7 +696,7 @@ void DisplayText(void)
Restore_graph(temp,bbuff);
break;
}
#endif
#endif // USE_SCRIPT_FATFS
{ int16_t num,gxp,gyp,gxs,gys,dec,icol;
float ymin,ymax;
var=atoiv(cp,&num);
@ -744,7 +744,7 @@ void DisplayText(void)
AddValue(num,temp);
}
break;
#endif
#endif // USE_GRAPH
#ifdef USE_AWATCH
case 'w':
@ -752,7 +752,7 @@ void DisplayText(void)
cp += var;
DrawAClock(temp);
break;
#endif
#endif // USE_AWATCH
#ifdef USE_TOUCH_BUTTONS
case 'b':
@ -834,12 +834,13 @@ void DisplayText(void)
buttons[num]->vpower.is_pushbutton=0;
}
if (dflg) buttons[num]->xdrawButton(buttons[num]->vpower.on_off);
buttons[num]->vpower.disable=!dflg;
}
}
}
}
break;
#endif
#endif // USE_TOUCH_BUTTONS
default:
// unknown escape
Response_P(PSTR("Unknown Escape"));
@ -1530,8 +1531,8 @@ void CmndDisplayRows(void)
bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale);
char get_jpeg_size(unsigned char* data, unsigned int data_size, unsigned short *width, unsigned short *height);
void rgb888_to_565(uint8_t *in, uint16_t *out, uint32_t len);
#endif
#endif
#endif // JPEG_PICTS
#endif // ESP32
#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT)
extern FS *fsp;
@ -1626,7 +1627,7 @@ void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) {
#endif // ESP32
}
}
#endif
#endif // USE_SCRIPT_FATFS
#ifdef USE_AWATCH
#define MINUTE_REDUCT 4
@ -1663,7 +1664,7 @@ void DrawAClock(uint16_t rad) {
temp=((float)RtcTime.minute*(pi/30.0)-(pi/2.0));
renderer->writeLine(disp_xpos, disp_ypos,disp_xpos+(frad-MINUTE_REDUCT)*cosf(temp),disp_ypos+(frad-MINUTE_REDUCT)*sinf(temp), fg_color);
}
#endif
#endif // USE_AWATCH
#ifdef USE_GRAPH
@ -1938,7 +1939,7 @@ void Restore_graph(uint8_t num, char *path) {
fp.close();
RedrawGraph(num,1);
}
#endif
#endif // USE_SCRIPT_FATFS
void RedrawGraph(uint8_t num, uint8_t flags) {
uint16_t index=num%NUM_GRAPHS;
@ -2050,16 +2051,13 @@ void AddValue(uint8_t num,float fval) {
#ifdef USE_FT5206
#include <FT5206.h>
// touch panel controller
#undef FT5206_address
#define FT5206_address 0x38
#include <FT5206.h>
FT5206_Class *touchp;
TP_Point pLoc;
extern VButton *buttons[];
bool FT5206_found;
bool Touch_Init(TwoWire &i2c) {
@ -2088,6 +2086,7 @@ uint32_t Touch_Status(uint32_t sel) {
}
}
#ifdef USE_TOUCH_BUTTONS
void Touch_MQTT(uint8_t index, const char *cp) {
ResponseTime_P(PSTR(",\"FT5206\":{\"%s%d\":\"%d\"}}"), cp, index+1, buttons[index]->vpower.on_off);
@ -2184,6 +2183,7 @@ uint8_t vbutt=0;
pLoc.y = 0;
}
}
#endif // USE_TOUCH_BUTTONS
#endif // USE_FT5206

View File

@ -21,7 +21,22 @@
// contains some definitions for functions used before their declarations
void ZigbeeZCLSend_Raw(uint16_t dtsAddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId);
class ZigbeeZCLSendMessage {
public:
uint16_t shortaddr;
uint16_t groupaddr;
uint16_t clusterId;
uint8_t endpoint;
uint8_t cmdId;
uint16_t manuf;
bool clusterSpecific;
bool needResponse;
uint8_t transacId; // ZCL transaction number
const uint8_t *msg;
size_t len;
};
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl);
// get the result as a string (const char*) and nullptr if there is no field or the string is empty
const char * getCaseInsensitiveConstCharNull(const JsonObject &json, const char *needle) {
@ -42,7 +57,7 @@ JsonVariant &startsWithCaseInsensitive(const JsonObject &json, const char *needl
return *(JsonVariant*)nullptr;
}
String needle_s(needle);
String needle_s((const __FlashStringHelper *)needle);
needle_s.toLowerCase();
for (auto kv : json) {

View File

@ -0,0 +1,752 @@
/*
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
Copyright (C) 2020 Theo Arends and Stephan Hadinger
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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
/*********************************************************************************************\
* Replacement libs for JSON to output a list of attributes
\*********************************************************************************************/
// simplified version of strcmp accepting both arguments to be in PMEM, and accepting nullptr arguments
// inspired from https://code.woboq.org/userspace/glibc/string/strcmp.c.html
int strcmp_PP(const char *p1, const char *p2) {
if (p1 == p2) { return 0; } // equality
if (!p1) { return -1; } // first string is null
if (!p2) { return 1; } // second string is null
const unsigned char *s1 = (const unsigned char *) p1;
const unsigned char *s2 = (const unsigned char *) p2;
unsigned char c1, c2;
do {
c1 = (unsigned char) pgm_read_byte(s1);
s1++;
c2 = (unsigned char) pgm_read_byte(s2);
s2++;
if (c1 == '\0')
return c1 - c2;
}
while (c1 == c2);
return c1 - c2;
}
/*********************************************************************************************\
*
* Variables for Rules from last Zigbee message received
*
\*********************************************************************************************/
typedef struct Z_LastMessageVars {
uint16_t device; // device short address
uint16_t groupaddr; // group address
uint16_t cluster; // cluster id
uint8_t endpoint; // source endpoint
} Z_LastMessageVars;
Z_LastMessageVars gZbLastMessage;
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\
*
* Class for single attribute
*
\*********************************************************************************************/
enum class Za_type : uint8_t {
Za_none, // empty, translates into null in JSON
// numericals
Za_bool, // boolean, translates to true/false, uses uval32 to store
Za_uint, // unsigned 32 int, uses uval32
Za_int, // signed 32 int, uses ival32
Za_float, // float 32, uses fval
// non-nummericals
Za_raw, // bytes buffer, uses bval
Za_str // string, uses sval
};
class Z_attribute {
public:
// attribute key, either cluster+attribute_id or plain name
union {
struct {
uint16_t cluster;
uint16_t attr_id;
} id;
char * key;
} key;
// attribute value
union {
uint32_t uval32;
int32_t ival32;
float fval;
SBuffer* bval;
char* sval;
} val;
Za_type type; // uint8_t in size, type of attribute, see above
bool key_is_str; // is the key a string?
bool key_is_pmem; // is the string in progmem, so we don't need to make a copy
bool val_str_raw; // if val is String, it is raw JSON and should not be escaped
uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1)
// Constructor with all defaults
Z_attribute():
key{ .id = { 0x0000, 0x0000 } },
val{ .uval32 = 0x0000 },
type(Za_type::Za_none),
key_is_str(false),
key_is_pmem(false),
val_str_raw(false),
key_suffix(1)
{};
Z_attribute(const Z_attribute & rhs) {
deepCopy(rhs);
}
Z_attribute & operator = (const Z_attribute & rhs) {
freeKey();
freeVal();
deepCopy(rhs);
}
// Destructor, free memory that was allocated
~Z_attribute() {
freeKey();
freeVal();
}
// free any allocated memoruy for values
void freeVal(void) {
switch (type) {
case Za_type::Za_raw:
if (val.bval) { delete val.bval; val.bval = nullptr; }
break;
case Za_type::Za_str:
if (val.sval) { delete[] val.sval; val.sval = nullptr; }
break;
}
}
// free any allocated memoruy for keys
void freeKey(void) {
if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
key.key = nullptr;
}
// set key name
void setKeyName(const char * _key, bool pmem = false) {
freeKey();
key_is_str = true;
key_is_pmem = pmem;
if (pmem) {
key.key = (char*) _key;
} else {
setKeyName(_key, nullptr);
}
}
// provide two entries and concat
void setKeyName(const char * _key, const char * _key2) {
freeKey();
key_is_str = true;
key_is_pmem = false;
if (_key) {
size_t key_len = strlen_P(_key);
if (_key2) {
key_len += strlen_P(_key2);
}
key.key = new char[key_len+1];
strcpy_P(key.key, _key);
if (_key2) {
strcat_P(key.key, _key2);
}
}
}
void setKeyId(uint16_t cluster, uint16_t attr_id) {
freeKey();
key_is_str = false;
key.id.cluster = cluster;
key.id.attr_id = attr_id;
}
// Setters
void setNone(void) {
freeVal(); // free any previously allocated memory
val.uval32 = 0;
type = Za_type::Za_none;
}
void setUInt(uint32_t _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_uint;
}
void setBool(bool _val) {
freeVal(); // free any previously allocated memory
val.uval32 = _val;
type = Za_type::Za_bool;
}
void setInt(int32_t _val) {
freeVal(); // free any previously allocated memory
val.ival32 = _val;
type = Za_type::Za_int;
}
void setFloat(float _val) {
freeVal(); // free any previously allocated memory
val.fval = _val;
type = Za_type::Za_float;
}
void setBuf(const SBuffer &buf, size_t index, size_t len) {
freeVal();
if (len) {
val.bval = new SBuffer(len);
val.bval->addBuffer(buf.buf(index), len);
}
type = Za_type::Za_raw;
}
// set the string value
// PMEM argument is allowed
// string will be copied, so it can be changed later
// nullptr is allowed and considered as empty string
// Note: memory is allocated only if string is non-empty
void setStr(const char * _val) {
freeVal(); // free any previously allocated memory
val_str_raw = false;
// val.sval is always nullptr after freeVal()
if (_val) {
size_t len = strlen_P(_val);
if (len) {
val.sval = new char[len+1];
strcpy_P(val.sval, _val);
}
}
type = Za_type::Za_str;
}
inline void setStrRaw(const char * _val) {
setStr(_val);
val_str_raw = true;
}
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
// get num values
float getFloat(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return (float) val.uval32;
case Za_type::Za_int: return (float) val.ival32;
case Za_type::Za_float: return val.fval;
default: return 0.0f;
}
}
int32_t getInt(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return (int32_t) val.uval32;
case Za_type::Za_int: return val.ival32;
case Za_type::Za_float: return (int32_t) val.fval;
default: return 0;
}
}
uint32_t getUInt(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return val.uval32;
case Za_type::Za_int: return (uint32_t) val.ival32;
case Za_type::Za_float: return (uint32_t) val.fval;
default: return 0;
}
}
bool getBool(void) const {
switch (type) {
case Za_type::Za_bool:
case Za_type::Za_uint: return val.uval32 ? true : false;
case Za_type::Za_int: return val.ival32 ? true : false;
case Za_type::Za_float: return val.fval ? true : false;
default: return false;
}
}
const SBuffer * getRaw(void) const {
if (Za_type::Za_raw == type) { return val.bval; }
return nullptr;
}
// always return a point to a string, if not defined then empty string.
// Never returns nullptr
const char * getStr(void) const {
if (Za_type::Za_str == type) { return val.sval; }
return "";
}
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const {
// check if keys are equal
if (key_is_str != attr2.key_is_str) { return false; }
if (key_is_str) {
if (strcmp_PP(key.key, attr2.key.key)) { return false; }
} else {
if ((key.id.cluster != attr2.key.id.cluster) ||
(key.id.attr_id != attr2.key.id.attr_id)) { return false; }
}
if (!ignore_key_suffix) {
if (key_suffix != attr2.key_suffix) { return false; }
}
return true;
}
bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const {
if (!key_is_str) {
if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
}
return false;
}
bool equalsKey(const char * name, uint8_t suffix = 0) const {
if (key_is_str) {
if (0 == strcmp_PP(key.key, name)) {
if (suffix) {
if (key_suffix == suffix) { return true; }
} else {
return true;
}
}
}
return false;
}
bool equalsVal(const Z_attribute & attr2) const {
if (type != attr2.type) { return false; }
if ((type >= Za_type::Za_bool) && (type <= Za_type::Za_float)) {
// numerical value
if (val.uval32 != attr2.val.uval32) { return false; }
} else if (type == Za_type::Za_raw) {
// compare 2 Static buffers
return equalsSBuffer(val.bval, attr2.val.bval);
} else if (type == Za_type::Za_str) {
// if (val_str_raw != attr2.val_str_raw) { return false; }
if (strcmp_PP(val.sval, attr2.val.sval)) { return false; }
}
return true;
}
bool equals(const Z_attribute & attr2) const {
return equalsKey(attr2) && equalsVal(attr2);
}
String toString(bool prefix_comma = false) const {
String res("");
if (prefix_comma) { res += ','; }
res += '"';
// compute the attribute name
if (key_is_str) {
if (key.key) { res += EscapeJSONString(key.key); }
else { res += F("null"); } // shouldn't happen
if (key_suffix > 1) {
res += key_suffix;
}
} else {
char attr_name[12];
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
res += attr_name;
if (key_suffix > 1) {
res += '+';
res += key_suffix;
}
}
res += F("\":");
// value part
switch (type) {
case Za_type::Za_none:
res += "null";
break;
case Za_type::Za_bool:
res += val.uval32 ? F("true") : F("false");
break;
case Za_type::Za_uint:
res += val.uval32;
break;
case Za_type::Za_int:
res += val.ival32;
break;
case Za_type::Za_float:
{
String fstr(val.fval, 2);
size_t last = fstr.length() - 1;
// remove trailing zeros
while (fstr[last] == '0') {
fstr.remove(last--);
}
// remove trailing dot
if (fstr[last] == '.') {
fstr.remove(last);
}
res += fstr;
}
break;
case Za_type::Za_raw:
res += '"';
if (val.bval) {
size_t blen = val.bval->len();
// print as HEX
char hex[2*blen+1];
ToHex_P(val.bval->getBuffer(), blen, hex, sizeof(hex));
res += hex;
}
res += '"';
break;
case Za_type::Za_str:
if (val_str_raw) {
if (val.sval) { res += val.sval; }
} else {
res += '"';
if (val.sval) {
res += EscapeJSONString(val.sval); // escape JSON chars
}
res += '"';
}
break;
}
return res;
}
// copy value from one attribute to another, without changing its type
void copyVal(const Z_attribute & rhs) {
freeVal();
// copy value
val.uval32 = 0x00000000;
type = rhs.type;
if (rhs.isNum()) {
val.uval32 = rhs.val.uval32;
} else if (rhs.type == Za_type::Za_raw) {
if (rhs.val.bval) {
val.bval = new SBuffer(rhs.val.bval->len());
val.bval->addBuffer(*(rhs.val.bval));
}
} else if (rhs.type == Za_type::Za_str) {
if (rhs.val.sval) {
size_t s_len = strlen_P(rhs.val.sval);
val.sval = new char[s_len+1];
strcpy_P(val.sval, rhs.val.sval);
}
}
val_str_raw = rhs.val_str_raw;
}
protected:
void deepCopy(const Z_attribute & rhs) {
// copy key
if (!rhs.key_is_str) {
key.id.cluster = rhs.key.id.cluster;
key.id.attr_id = rhs.key.id.attr_id;
} else {
if (rhs.key_is_pmem) {
key.key = rhs.key.key; // PMEM, don't copy
} else {
key.key = nullptr;
if (rhs.key.key) {
size_t key_len = strlen_P(rhs.key.key);
if (key_len) {
key.key = new char[key_len+1];
strcpy_P(key.key, rhs.key.key);
}
}
}
}
key_is_str = rhs.key_is_str;
key_is_pmem = rhs.key_is_pmem;
key_suffix = rhs.key_suffix;
// copy value
copyVal(rhs);
// don't touch next pointer
}
};
/*********************************************************************************************\
*
* Class for attribute array of values
* This is a helper function to generate a clean list of unsigned ints
*
\*********************************************************************************************/
class Z_json_array {
public:
Z_json_array(): val("[]") {} // start with empty array
void add(uint32_t uval32) {
// remove trailing ']'
val.remove(val.length()-1);
if (val.length() > 1) { // if not empty, prefix with comma
val += ',';
}
val += uval32;
val += ']';
}
String &toString(void) {
return val;
}
private :
String val;
};
/*********************************************************************************************\
*
* Class for attribute ordered list
*
\*********************************************************************************************/
// Attribute list
// Contains meta-information:
// - source endpoint (is conflicting)
// - LQI (if not conflicting)
class Z_attribute_list : public LList<Z_attribute> {
public:
uint8_t src_ep; // source endpoint, 0xFF if unknown
uint8_t lqi; // linkquality, 0xFF if unknown
uint16_t group_id; // group address OxFFFF if inknown
Z_attribute_list():
LList<Z_attribute>(), // call superclass constructor
src_ep(0xFF),
lqi(0xFF),
group_id(0xFFFF)
{};
// we don't define any destructor, the superclass destructor is automatically called
// reset object to its initial state
// free all allocated memory
void reset(void) {
LList<Z_attribute>::reset();
src_ep = 0xFF;
lqi = 0xFF;
group_id = 0xFFFF;
}
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; }
inline bool isValidLQI(void) const { return 0xFF != lqi; }
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; }
// the following addAttribute() compute the suffix and increments it
// Add attribute to the list, given cluster and attribute id
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
// Add attribute to the list, given name
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
inline Z_attribute & addAttribute(const __FlashStringHelper * name, uint8_t suffix = 0) {
return addAttribute((const char*) name, true, suffix);
}
// Remove from list by reference, if null or not found, then do nothing
inline void removeAttribute(const Z_attribute * attr) { remove(attr); }
// dump the entire structure as JSON, starting from head (as parameter)
// does not start not end with a comma
// do we enclosed in brackets '{' '}'
String toString(bool enclose_brackets = false) const;
// find if attribute with same key already exists, return null if not found
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis always count here
// non-const variants
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
}
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
}
inline Z_attribute * findAttribute(const Z_attribute &attr) {
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(attr);
}
// count matching attributes, ignoring suffix
size_t countAttribute(uint16_t cluster, uint16_t attr_id) const ;
size_t countAttribute(const char * name) const ;
// if suffix == 0, we don't care and find the first match
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
// always care about suffix
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
// replace attribute with new value, suffix does care
Z_attribute & replaceOrCreate(const Z_attribute &attr);
// merge with secondary list, return true if ok, false if conflict
bool mergeList(const Z_attribute_list &list2);
};
// add a cluster/attr_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.key.id.cluster = cluster;
attr.key.id.attr_id = attr_id;
attr.key_is_str = false;
if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.attr_id); }
else { attr.key_suffix = suffix; }
return attr;
}
// add a cluster/attr_id attribute at the end of the list
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, pmem);
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
else { attr.key_suffix = suffix; }
return attr;
}
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
Z_attribute & attr = addToLast();
attr.setKeyName(name, name2);
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
else { attr.key_suffix = suffix; }
return attr;
}
String Z_attribute_list::toString(bool enclose_brackets) const {
String res = "";
if (enclose_brackets) { res += '{'; }
bool prefix_comma = false;
for (const auto & attr : *this) {
res += attr.toString(prefix_comma);
prefix_comma = true;
}
// add source endpoint
if (0xFF != src_ep) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
res += src_ep;
}
// add group address
if (0xFFFF != group_id) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
res += group_id;
}
// add lqi
if (0xFF != lqi) {
if (prefix_comma) { res += ','; }
prefix_comma = true;
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");
res += lqi;
}
if (enclose_brackets) { res += '}'; }
// done
return res;
}
// suffis always count here
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
uint8_t suffix = attr.key_suffix;
if (attr.key_is_str) {
return findAttribute(attr.key.key, suffix);
} else {
return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, suffix);
}
}
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsKey(cluster, attr_id, suffix)) { return &attr; }
}
return nullptr;
}
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
size_t count = 0;
for (const auto & attr : *this) {
if (attr.equalsKey(cluster, attr_id, 0)) { count++; }
}
return count;
}
// return the existing attribute or create a new one
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
return found ? *found : addAttribute(cluster, attr_id, suffix);
}
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
for (const auto & attr : *this) {
if (attr.equalsKey(name, suffix)) { return &attr; }
}
return nullptr;
}
size_t Z_attribute_list::countAttribute(const char * name) const {
size_t count = 0;
for (const auto & attr : *this) {
if (attr.equalsKey(name, 0)) { count++; }
}
return count;
}
// return the existing attribute or create a new one
Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t suffix) {
Z_attribute * found = findAttribute(name, suffix);
return found ? *found : addAttribute(name, suffix);
}
// same but passing a Z_attribute as key
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
if (attr.key_is_str) {
return findOrCreateAttribute(attr.key.key, attr.key_suffix);
} else {
return findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
}
}
// replace the entire content with new attribute or create
Z_attribute & Z_attribute_list::replaceOrCreate(const Z_attribute &attr) {
Z_attribute &new_attr = findOrCreateAttribute(attr);
new_attr.copyVal(attr);
return new_attr;
}
bool Z_attribute_list::mergeList(const Z_attribute_list &attr_list) {
// Check source endpoint
if (0xFF == src_ep) {
src_ep = attr_list.src_ep;
} else if (0xFF != attr_list.src_ep) {
if (src_ep != attr_list.src_ep) { return false; }
}
if (0xFF != attr_list.lqi) {
lqi = attr_list.lqi;
}
for (auto & attr : attr_list) {
replaceOrCreate(attr);
}
return true;
}
#endif // USE_ZIGBEE

View File

@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
#include <vector>
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif
@ -29,25 +27,6 @@ const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait f
/*********************************************************************************************\
* Structures for Rules variables related to the last received message
\*********************************************************************************************/
typedef struct Z_LastMessageVars {
uint16_t device; // device short address
uint16_t groupaddr; // group address
uint16_t cluster; // cluster id
uint8_t endpoint; // source endpoint
} Z_LastMessageVars;
Z_LastMessageVars gZbLastMessage;
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\
* Structures for device configuration
\*********************************************************************************************/
const size_t endpoints_max = 8; // we limit to 8 endpoints
class Z_Device {
@ -57,10 +36,13 @@ public:
char * manufacturerId;
char * modelId;
char * friendlyName;
// _defer_last_time : what was the last time an outgoing message is scheduled
// this is designed for flow control and avoid messages to be lost or unanswered
uint32_t defer_last_message_sent;
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
// json buffer used for attribute reporting
DynamicJsonBuffer *json_buffer;
JsonObject *json;
// Used for attribute reporting
Z_attribute_list attr_list;
// sequence number for Zigbee frames
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint8_t seqNumber;
@ -95,14 +77,14 @@ public:
int16_t mains_power; // Active power
// Constructor with all defaults
Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00):
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
longaddr(_longaddr),
manufacturerId(nullptr),
modelId(nullptr),
friendlyName(nullptr),
defer_last_message_sent(0),
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
json_buffer(nullptr),
json(nullptr),
attr_list(),
shortaddr(_shortaddr),
seqNumber(0),
// Hue support
@ -168,21 +150,28 @@ public:
* Structures for deferred callbacks
\*********************************************************************************************/
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
typedef void (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value);
// Category for Deferred actions, this allows to selectively remove active deferred or update them
typedef enum Z_Def_Category {
Z_CAT_NONE = 0, // no category, it will happen anyways
Z_CAT_ALWAYS = 0, // no category, it will happen whatever new timers
// Below will clear any event in the same category for the same address (shortaddr / groupaddr)
Z_CLEAR_DEVICE = 0x01,
Z_CAT_READ_ATTR, // Attribute reporting, either READ_ATTRIBUTE or REPORT_ATTRIBUTE, we coalesce all attributes reported if we can
Z_CAT_VIRTUAL_OCCUPANCY, // Creation of a virtual attribute, typically after a time-out. Ex: Aqara presence sensor
Z_CAT_REACHABILITY, // timer set to measure reachability of device, i.e. if we don't get an answer after 1s, it is marked as unreachable (for Alexa)
Z_CAT_READ_0006, // Read 0x0006 cluster
Z_CAT_READ_0008, // Read 0x0008 cluster
Z_CAT_READ_0102, // Read 0x0300 cluster
Z_CAT_READ_0300, // Read 0x0300 cluster
// Below will clear based on device + cluster pair.
Z_CLEAR_DEVICE_CLUSTER,
Z_CAT_READ_CLUSTER,
// Below will clear based on device + cluster + endpoint
Z_CLEAR_DEVICE_CLUSTER_ENDPOINT,
Z_CAT_EP_DESC, // read endpoint descriptor to gather clusters
Z_CAT_BIND, // send auto-binding to coordinator
Z_CAT_CONFIG_ATTR, // send a config attribute reporting request
Z_CAT_READ_ATTRIBUTE, // read a single attribute
} Z_Def_Category;
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 1000; // 1000 ms or 1s
const uint32_t Z_CAT_REACHABILITY_TIMEOUT = 2000; // 1000 ms or 1s
typedef struct Z_Deferred {
// below are per device timers, used for example to query the new state of the device
@ -207,7 +196,7 @@ typedef struct Z_Deferred {
// - shortaddr and longaddr cannot be both null
class Z_Devices {
public:
Z_Devices() {};
Z_Devices() : _deferred() {};
// Probe the existence of device keys
// Results:
@ -218,12 +207,17 @@ public:
uint16_t isKnownIndex(uint32_t index) const;
uint16_t isKnownFriendlyName(const char * name) const;
Z_Device & findShortAddr(uint16_t shortaddr);
const Z_Device & findShortAddr(uint16_t shortaddr) const;
Z_Device & findLongAddr(uint64_t longaddr);
const Z_Device & findLongAddr(uint64_t longaddr) const;
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
// check if a device was found or if it's the fallback device
inline bool foundDevice(const Z_Device & device) const {
return (&device != &device_unk);
}
int32_t findLongAddr(uint64_t longaddr) const;
int32_t findFriendlyName(const char * name) const;
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
@ -276,24 +270,28 @@ public:
bool isHueBulbHidden(uint16_t shortaddr) const ;
// Timers
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category);
void resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster = 0xFFFF, uint8_t endpoint = 0xFF);
void setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
void queueTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func);
void runTimer(void);
// Append or clear attributes Json structure
void jsonClear(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
const JsonObject *jsonGet(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
// Iterator
size_t devicesSize(void) const {
return _devices.size();
return _devices.length();
}
const Z_Device & devicesAt(size_t i) const {
const Z_Device * devp = _devices.at(i);
if (devp) {
return *devp;
} else {
return device_unk;
}
const Z_Device &devicesAt(size_t i) const {
return *(_devices.at(i));
}
// Remove device from list
@ -308,8 +306,8 @@ public:
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
private:
std::vector<Z_Device*> _devices = {};
std::vector<Z_Deferred> _deferred = {}; // list of deferred calls
LList<Z_Device> _devices; // list of devices
LList<Z_Deferred> _deferred; // list of deferred calls
uint32_t _saveTimer = 0;
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
@ -317,10 +315,7 @@ private:
// Any find() function will not return Null, instead it will return this instance
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
template < typename T>
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
int32_t findShortAddrIdx(uint16_t shortaddr) const;
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
// Create a new entry in the devices list - must be called if it is sure it does not already exist
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
void freeDeviceEntry(Z_Device *device);
@ -343,31 +338,16 @@ uint16_t localShortAddr = 0;
* Implementation
\*********************************************************************************************/
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
template < typename T>
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
// Find given element in vector
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
if (it != vecOfElements.end()) {
return true;
} else {
return false;
}
}
//
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
// entry with same shortaddr or longaddr exists.
//
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
Z_Device * device_alloc = new Z_Device(shortaddr, longaddr);
Z_Device device(shortaddr, longaddr);
device_alloc->json_buffer = new DynamicJsonBuffer(16);
_devices.push_back(device_alloc);
dirty();
return *(_devices.back());
return _devices.addHead(device);
}
void Z_Devices::freeDeviceEntry(Z_Device *device) {
@ -383,20 +363,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
// In:
// shortaddr (not BAD_SHORTADDR)
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const {
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
int32_t found = 0;
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return found; }
found++;
// reference to device, or to device_unk if not found
// (use foundDevice() to check if found)
Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) {
for (auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return *elem; }
for (const auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return device_unk;
}
@ -408,14 +385,19 @@ const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return -1; }
int32_t found = 0;
Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
for (auto &elem : _devices) {
if (elem->longaddr == longaddr) { return found; }
found++;
if (elem.longaddr == longaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return device_unk; }
for (const auto &elem : _devices) {
if (elem.longaddr == longaddr) { return elem; }
}
return device_unk;
}
//
// Scan all devices to find a corresponding friendlyNme
@ -431,8 +413,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
int32_t found = 0;
if (name_len) {
for (auto &elem : _devices) {
if (elem->friendlyName) {
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
if (elem.friendlyName) {
if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
}
found++;
}
@ -441,9 +423,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
}
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
int32_t found = findLongAddr(longaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device.shortaddr; // can be zero, if not yet registered
} else {
return BAD_SHORTADDR;
@ -471,7 +452,7 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
}
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
return findShortAddr(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
}
//
@ -479,38 +460,28 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
//
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
return device;
}
//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found);
return createDeviceEntry(shortaddr, 0);
}
// Same version but Const
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
}
return device_unk;
}
// find the Device object by its longaddr (unique key if not null)
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
int32_t found = findLongAddr(longaddr);
if (found > 0) {
return *(_devices[found]);
Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device;
}
return createDeviceEntry(0, longaddr);
}
// Remove device from list, return true if it was known, false if it was not recorded
bool Z_Devices::removeDevice(uint16_t shortaddr) {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
freeDeviceEntry(_devices.at(found));
_devices.erase(_devices.begin() + found);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
_devices.remove(&device);
dirty();
return true;
}
@ -523,27 +494,27 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
// shortaddr
// longaddr (both can't be null at the same time)
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry
int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
if (foundDevice(*s_found) && foundDevice(*l_found)) { // both shortaddr and longaddr are already registered
if (s_found == l_found) {
} else { // they don't match
// the device with longaddr got a new shortaddr
_devices[l_found]->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
l_found->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
// erase the previous shortaddr
freeDeviceEntry(_devices.at(s_found));
_devices.erase(_devices.begin() + s_found);
freeDeviceEntry(s_found);
_devices.remove(s_found);
dirty();
}
} else if (s_found >= 0) {
} else if (foundDevice(*s_found)) {
// shortaddr already exists but longaddr not
// add the longaddr to the entry
_devices[s_found]->longaddr = longaddr;
s_found->longaddr = longaddr;
dirty();
} else if (l_found >= 0) {
} else if (foundDevice(*l_found)) {
// longaddr entry exists, update shortaddr
_devices[l_found]->shortaddr = shortaddr;
l_found->shortaddr = shortaddr;
dirty();
} else {
// neither short/lonf addr are found.
@ -588,9 +559,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
//
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
uint32_t count_ep = 0;
int32_t found = findShortAddrIdx(shortaddr);
if (found < 0) return 0; // avoid creating an entry if the device was never seen
const Z_Device &device = devicesAt(found);
const Z_Device & device =findShortAddr(shortaddr);
if (!foundDevice(device)) return 0;
for (uint32_t i = 0; i < endpoints_max; i++) {
if (0 != device.endpoints[i]) {
@ -666,9 +636,8 @@ void Z_Devices::setBatteryPercent(uint16_t shortaddr, uint8_t bp) {
// 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 = findShortAddrIdx(shortaddr);
if (short_found >= 0) {
Z_Device &device = getShortAddr(shortaddr);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
device.seqNumber += 1;
return device.seqNumber;
} else {
@ -754,9 +723,9 @@ void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
}
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
uint8_t zb_profile = _devices[found]->zb_profile;
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
uint8_t zb_profile = device.zb_profile;
if (0x00 == (zb_profile & 0xF0)) {
// bulb type
return (zb_profile & 0x08) ? true : false;
@ -767,15 +736,17 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
// Deferred actions
// Parse for a specific category, of all deferred for a device if category == 0xFF
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) {
// Only with specific cluster number or for all clusters if cluster == 0xFFFF
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category, uint16_t cluster, uint8_t endpoint) {
// iterate the list of deferred, and remove any linked to the shortaddr
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
// Notice that the iterator is decremented after it is passed
// to erase() but before erase() is executed
// see https://www.techiedelight.com/remove-elements-vector-inside-loop-cpp/
if ((it->shortaddr == shortaddr) && (it->groupaddr == groupaddr)) {
if ((0xFF == category) || (it->category == category)) {
_deferred.erase(it--);
for (auto & defer : _deferred) {
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
if ((0xFF == category) || (defer.category == category)) {
if ((0xFFFF == cluster) || (defer.cluster == cluster)) {
if ((0xFF == endpoint) || (defer.endpoint == endpoint)) {
_deferred.remove(&defer);
}
}
}
}
}
@ -784,12 +755,13 @@ void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uin
// Set timer for a specific device
void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func) {
// First we remove any existing timer for same device in same category, except for category=0x00 (they need to happen anyway)
if (category) { // if category == 0, we leave all previous
resetTimersForDevice(shortaddr, groupaddr, category); // remove any cluster
if (category >= Z_CLEAR_DEVICE) { // if category == 0, we leave all previous timers
resetTimersForDevice(shortaddr, groupaddr, category, category >= Z_CLEAR_DEVICE_CLUSTER ? cluster : 0xFFFF, category >= Z_CLEAR_DEVICE_CLUSTER_ENDPOINT ? endpoint : 0xFF); // remove any cluster
}
// Now create the new timer
Z_Deferred deferred = { wait_ms + millis(), // timer
Z_Deferred & deferred = _deferred.addHead();
deferred = { wait_ms + millis(), // timer
shortaddr,
groupaddr,
cluster,
@ -797,20 +769,32 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
category,
value,
func };
_deferred.push_back(deferred);
}
// Set timer after the already queued events
// I.e. the wait_ms is not counted from now, but from the last event queued, which is 'now' or in the future
void Z_Devices::queueTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_ms, uint16_t cluster, uint8_t endpoint, uint8_t category, uint32_t value, Z_DeviceTimer func) {
Z_Device & device = getShortAddr(shortaddr);
uint32_t now_millis = millis();
if (TimeReached(device.defer_last_message_sent)) {
device.defer_last_message_sent = now_millis;
}
// defer_last_message_sent equals now or a value in the future
device.defer_last_message_sent += wait_ms;
// for queueing we don't clear the backlog, so we force category to Z_CAT_ALWAYS
setTimer(shortaddr, groupaddr, (device.defer_last_message_sent - now_millis), cluster, endpoint, Z_CAT_ALWAYS, value, func);
}
// Run timer at each tick
// WARNING: don't set a new timer within a running timer, this causes memory corruption
void Z_Devices::runTimer(void) {
// visit all timers
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
Z_Deferred &defer = *it;
for (auto & defer : _deferred) {
uint32_t timer = defer.timer;
if (TimeReached(timer)) {
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
_deferred.erase(it--); // remove from list
_deferred.remove(&defer);
}
}
@ -821,160 +805,86 @@ void Z_Devices::runTimer(void) {
}
}
// Clear the JSON buffer for coalesced and deferred attributes
void Z_Devices::jsonClear(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
device.json = nullptr;
device.json_buffer->clear();
}
// Copy JSON from one object to another, this helps preserving the order of attributes
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
// first remove the potentially existing key in the target JSON, so new adds will be at the end of the list
to.remove(key); // force remove to have metadata like LinkQuality at the end
if (val.is<char*>()) {
const char * sval = val.as<char*>(); // using char* forces a copy, and also captures 'null' values
to.set(key, (char*) sval);
} else if (val.is<JsonArray>()) {
JsonArray &nested_arr = to.createNestedArray(key);
CopyJsonArray(nested_arr, val.as<JsonArray>()); // deep copy
} else if (val.is<JsonObject>()) {
JsonObject &nested_obj = to.createNestedObject(key);
CopyJsonObject(nested_obj, val.as<JsonObject>()); // deep copy
} else {
to.set(key, val); // general case for non array, object or string
}
}
// Shallow copy of array, we skip any sub-array or sub-object. It may be added in the future
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
for (auto v : arr) {
if (v.is<char*>()) {
String sval = v.as<String>(); // force a copy of the String value
to.add(sval);
} else if (v.is<JsonArray>()) {
} else if (v.is<JsonObject>()) {
} else {
to.add(v);
}
}
}
// Deep copy of object
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
for (auto kv : from) {
String key_string = kv.key;
JsonVariant &val = kv.value;
CopyJsonVariant(to, key_string, val);
}
}
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
// false - new attributes can be safely added
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return false; }
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const {
const Z_Device & device = findShortAddr(shortaddr);
if (nullptr == device.json) {
if (!foundDevice(device)) { return false; }
if (attr_list.isEmpty()) {
return false; // if no previous value, no conflict
}
// compare groups
// Special case for group addresses. Group attribute is only present if the target
// address is a group address, so just comparing attributes will not work.
// Eg: if the first packet has no group attribute, and the second does, conflict would not be detected
// Here we explicitly compute the group address of both messages, and compare them. No group means group=0x0000
// (we use the property of an missing attribute returning 0)
// (note: we use .get() here which is case-sensitive. We know however that the attribute was set with the exact syntax D_CMND_ZIGBEE_GROUP, so we don't need a case-insensitive get())
uint16_t group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
uint16_t group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
if (group1 != group2) {
return true; // if group addresses differ, then conflict
if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) {
if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
}
// compare src_ep
if (device.attr_list.isValidSrcEp() && attr_list.isValidSrcEp()) {
if (device.attr_list.src_ep != attr_list.src_ep) { return true; }
}
// LQI does not count as conflicting
// parse all other parameters
for (auto kv : values) {
String key_string = kv.key;
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
// ignore group, it was handled already
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
// attribute "Endpoint" or "Group"
if (device.json->containsKey(kv.key)) {
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
return true;
}
}
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
if (device.json->containsKey(kv.key)) {
return true; // conflict!
for (const auto & attr : attr_list) {
const Z_attribute * curr_attr = device.attr_list.findAttribute(attr);
if (nullptr != curr_attr) {
if (!curr_attr->equalsVal(attr)) {
return true; // the value already exists and is different - conflict!
}
}
}
return false;
}
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return; }
if (nullptr == device.json) {
device.json = &(device.json_buffer->createObject());
}
// Prepend Device, will be removed later if redundant
char sa[8];
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
// Prepend Friendly Name if it has one
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
if (fname) {
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*) fname); // (char*) forces ArduinoJson to make a copy of the cstring
}
// copy all values from 'values' to 'json'
CopyJsonObject(*device.json, values);
}
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
return getShortAddr(shortaddr).json;
device.attr_list.mergeList(attr_list);
}
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
if (!device.valid()) { return; } // safeguard
JsonObject & json = *device.json;
if (&json == nullptr) { return; } // abort if nothing in buffer
Z_attribute_list &attr_list = device.attr_list;
if (!attr_list.isEmpty()) {
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
// save parameters is global variables to be used by Rules
gZbLastMessage.device = shortaddr; // %zbdevice%
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
// dump json in string
String msg = "";
json.printTo(msg);
zigbee_devices.jsonClear(shortaddr);
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
mqtt_data[0] = 0; // clear string
// Do we prefix with `ZbReceived`?
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
}
// What key do we use, shortaddr or name?
if (use_fname) {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"%s\":%s}"), fname, msg.c_str());
Response_P(PSTR("%s{\"%s\":{"), mqtt_data, fname);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname, msg.c_str());
Response_P(PSTR("%s{\"0x%04X\":{"), mqtt_data, shortaddr);
}
} else {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"0x%04X\":%s}"), shortaddr, msg.c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
// Add "Device":"0x...."
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), mqtt_data, shortaddr);
// Add "Name":"xxx" if name is present
if (fname) {
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), mqtt_data, EscapeJSONString(fname).c_str());
}
// Add all other attributes
Response_P(PSTR("%s%s}}"), mqtt_data, attr_list.toString().c_str());
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("%s}"), mqtt_data);
}
// AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> %s"), mqtt_data); // TODO
attr_list.reset(); // clear the attributes
if (Settings.flag4.zigbee_distinct_topics) {
char subtopic[16];
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
@ -983,11 +893,12 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
XdrvRulesProcess(); // apply rules
}
}
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
void Z_Devices::jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list) {
jsonPublishFlush(shortaddr); // flush any previous buffer
jsonAppend(shortaddr, values);
jsonAppend(shortaddr, attr_list);
jsonPublishFlush(shortaddr); // publish now
}
@ -1044,9 +955,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
JsonObject& json = jsonBuffer.createObject();
char hex[8];
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
const char * fname = getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
@ -1090,8 +1000,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
JsonArray& json = jsonBuffer.createArray();
JsonArray& devices = json;
for (std::vector<Z_Device*>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
const Z_Device &device = **it;
for (const auto & device : _devices) {
uint16_t shortaddr = device.shortaddr;
char hex[22];

File diff suppressed because it is too large Load Diff

View File

@ -148,7 +148,7 @@ const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007), ZLE(0x0008) }; // Hue, Sat, X, Y, CT, ColorMode
// 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 groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
size_t attrs_len = 0;
const uint8_t* attrs = nullptr;
@ -174,43 +174,28 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
if (groupaddr) {
shortaddr = BAD_SHORTADDR; // if group address, don't send to device
}
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
groupaddr,
cluster /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0, /* manuf */
false /* not cluster specific */,
true /* response */,
seq, /* zcl transaction id */
attrs, attrs_len
}));
}
return 0; // Fix GCC 10.1 warning
}
// This callback is registered after a an attribute read command was made to a light, and fires if we don't get any response after 1000 ms
int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
if (BAD_SHORTADDR != shortaddr) {
zigbee_devices.setReachable(shortaddr, false); // mark device as reachable
}
return 0; // Fix GCC 10.1 warning
}
// set a timer to read back the value in the future
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint) {
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006: // for On/Off
case 0x0009: // for Alamrs
wait_ms = 200; // wait 0.2 s
break;
case 0x0008: // for Dimmer
case 0x0300: // for Color
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102: // for Shutters
wait_ms = 10000; // wait 10.0 s
break;
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
// returns true if char is 'x', 'y' or 'z'
@ -280,61 +265,10 @@ void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz)
}
}
// 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
void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t cmd, bool direction) {
if (direction) { return; } // no need to update if server->client
int32_t z_cat = -1;
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006:
z_cat = Z_CAT_READ_0006;
wait_ms = 200; // wait 0.2 s
break;
case 0x0008:
z_cat = Z_CAT_READ_0008;
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102:
z_cat = Z_CAT_READ_0102;
wait_ms = 10000; // wait 10.0 s
break;
case 0x0300:
z_cat = Z_CAT_READ_0300;
wait_ms = 1050; // wait 1.0 s
break;
default:
break;
}
if (z_cat >= 0) {
uint8_t endpoint = 0;
if (BAD_SHORTADDR != shortaddr) {
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
}
if ((BAD_SHORTADDR == shortaddr) || (endpoint)) { // send if group address or endpoint is known
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback);
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
}
// Parse a cluster specific command, and try to convert into human readable
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, 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;
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
const char * command_name = nullptr;
uint8_t conv_direction;
Z_XYZ_Var xyz;
@ -373,7 +307,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
p += 2;
}
if (match) {
command_name = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->tasmota_cmd_offset));
command_name = Z_strings + pgm_read_word(&conv->tasmota_cmd_offset);
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
if (0xFF == conv_cmd) {
// shift all values
@ -396,112 +330,109 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" 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);
attr_list.addAttribute(attrid_str).setBuf(payload, 0, payload.len());
if (command_name) {
// Now try to transform into a human readable format
String command_name2 = String(command_name);
// if (direction & 0x80) then specific transform
if (conv_direction & 0x80) {
// TODO need to create a specific command
uint32_t cccc00mm = (cluster << 16) | cmd; // format = cccc00mm, cccc = cluster, mm = command
// IAS
if ((cluster == 0x0500) && (cmd == 0x00)) {
// "ZoneStatusChange"
json[command_name] = xyz.x;
switch (cccc00mm) {
case 0x05000000: // "ZoneStatusChange"
attr_list.addAttribute(command_name, true).setUInt(xyz.x);
if (0 != xyz.y) {
json[command_name2 + F("Ext")] = xyz.y;
attr_list.addAttribute(command_name, PSTR("Ext")).setUInt(xyz.y);
}
if ((0 != xyz.z) && (0xFF != xyz.z)) {
json[command_name2 + F("Zone")] = xyz.z;
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
}
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
json[command_name] = xyz.y;
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
// GetGroupResp
json[command_name2 + F("Capacity")] = xyz.x;
json[command_name2 + F("Count")] = xyz.y;
JsonArray &arr = json.createNestedArray(command_name);
// for now convert alamrs 1 and 2 to Occupancy
// TODO we may only do this conversion to ZoneType == 0x000D 'Motion Sensor'
// Occupancy is 0406/0000 of type Zmap8
attr_list.addAttribute(0x0406, 0x0000).setUInt((xyz.x) & 0x01 ? 1 : 0);
break;
case 0x00040000:
case 0x00040001:
case 0x00040003: // AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
attr_list.addAttribute(command_name, true).setUInt(xyz.y);
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
break;
case 0x00040002: // GetGroupResp
attr_list.addAttribute(command_name, PSTR("Capacity")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
{
Z_json_array group_list;
for (uint32_t i = 0; i < xyz.y; i++) {
arr.add(payload.get16(2 + 2*i));
group_list.add(payload.get16(2 + 2*i));
}
} else if ((cluster == 0x0005) && ((cmd == 0x00) || (cmd == 0x02) || (cmd == 0x03))) {
// AddScene or RemoveScene or StoreScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
} else if ((cluster == 0x0005) && (cmd == 0x01)) {
// ViewScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0005) && (cmd == 0x03)) {
// RemoveAllScenes
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
} else if ((cluster == 0x0005) && (cmd == 0x06)) {
// GetSceneMembership
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("Capacity")] = xyz.y;
json[F("GroupId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0006) && (cmd == 0x40)) {
// Power Off With Effect
json[F("Power")] = 0; // always "Power":0
json[F("PowerEffect")] = xyz.x;
json[F("PowerEffectVariant")] = xyz.y;
} else if ((cluster == 0x0006) && (cmd == 0x41)) {
// Power On With Recall Global Scene
json[F("Power")] = 1; // always "Power":1
json[F("PowerRecallGlobalScene")] = true;
} else if ((cluster == 0x0006) && (cmd == 0x42)) {
// Power On With Timed Off Command
json[F("Power")] = 1; // always "Power":1
json[F("PowerOnlyWhenOn")] = xyz.x;
json[F("PowerOnTime")] = xyz.y / 10.0f;
json[F("PowerOffWait")] = xyz.z / 10.0f;
attr_list.addAttribute(command_name, true).setStrRaw(group_list.toString().c_str());
}
break;
case 0x00050000:
case 0x00050001: // ViewScene
case 0x00050002:
case 0x00050004: // AddScene or RemoveScene or StoreScene
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("SceneId"), true).setUInt(xyz.z);
if (0x00050001 == cccc00mm) { // ViewScene specific
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
}
break;
case 0x00050003: // RemoveAllScenes
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
break;
case 0x00050006: // GetSceneMembership
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("Capacity"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.z);
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
break;
case 0x00060040: // Power Off With Effect
attr_list.addAttribute(PSTR("Power"), true).setUInt(0);
attr_list.addAttribute(PSTR("PowerEffect"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerEffectVariant"), true).setUInt(xyz.y);
break;
case 0x00060041: // Power On With Recall Global Scene
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerRecallGlobalScene"), true).setBool(true);
break;
case 0x00060042: // Power On With Timed Off Command
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerOnlyWhenOn"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerOnTime"), true).setFloat(xyz.y / 10.0f);
attr_list.addAttribute(PSTR("PowerOffWait"), true).setFloat(xyz.z / 10.0f);
break;
}
} else { // general case
bool extended_command = false; // do we send command with endpoint suffix
// do we send command with endpoint suffix
char command_suffix[4] = { 0x00 }; // empty string by default
// if SO101 and multiple endpoints, append endpoint number
if (Settings.flag4.zb_index_ep) {
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
command_name2 += srcendpoint;
extended_command = true;
snprintf_P(command_suffix, sizeof(command_suffix), PSTR("%d"), srcendpoint);
}
}
if (0 == xyz.x_type) {
json[command_name] = true; // no parameter
if (extended_command) { json[command_name2] = true; }
attr_list.addAttribute(command_name, command_suffix).setBool(true);
} else if (0 == xyz.y_type) {
json[command_name] = xyz.x; // 1 parameter
if (extended_command) { json[command_name2] = xyz.x; }
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
} else {
// multiple answers, create an array
JsonArray &arr = json.createNestedArray(command_name);
Z_json_array arr;
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
if (extended_command) {
JsonArray &arr = json.createNestedArray(command_name2);
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
}
attr_list.addAttribute(command_name, command_suffix).setStrRaw(arr.toString().c_str());
}
}
}

View File

@ -681,7 +681,7 @@ ZBM(ZBS_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*false*/, 0
ZBM(ZBR_SET_CONCENTRATOR, EZSP_setConcentrator, 0x00 /*high*/, 0x00 /*ok*/) // 100000
// setInitialSecurityState
#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY | EMBER_NO_FRAME_COUNTER_RESET
#define EZ_SECURITY_MODE EMBER_TRUST_CENTER_GLOBAL_LINK_KEY | EMBER_PRECONFIGURED_NETWORK_KEY_MODE | EMBER_HAVE_NETWORK_KEY | EMBER_HAVE_PRECONFIGURED_KEY
ZBR(ZBS_SET_SECURITY, EZSP_setInitialSecurityState, 0x00 /*high*/,
Z_B0(EZ_SECURITY_MODE), Z_B1(EZ_SECURITY_MODE),
// preConfiguredKey

View File

@ -533,7 +533,11 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
#endif
for (uint32_t i = 0; i < activeEpCount; i++) {
zigbee_devices.addEndpoint(nwkAddr, activeEpList[i]);
uint8_t ep = activeEpList[i];
zigbee_devices.addEndpoint(nwkAddr, ep);
if ((i < 4) && (ep < 0x10)) {
zigbee_devices.queueTimer(nwkAddr, 0 /* groupaddr */, 1500, ep /* fake cluster as ep */, ep, Z_CAT_EP_DESC, 0 /* value */, &Z_SendSimpleDescReq);
}
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
@ -546,7 +550,132 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
Z_SendAFInfoRequest(nwkAddr); // probe for ModelId and ManufId
Z_SendDeviceInfoRequest(nwkAddr); // probe for ModelId and ManufId
return -1;
}
// list of clusters that need bindings
const uint8_t Z_bindings[] PROGMEM = {
Cx0001, Cx0006, Cx0008, Cx0300,
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
Cx0500,
};
int32_t Z_ClusterToCxBinding(uint16_t cluster) {
uint8_t cx = ClusterToCx(cluster);
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (pgm_read_byte(&Z_bindings[i]) == cx) {
return i;
}
}
return -1;
}
void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf,
size_t in_index, size_t in_len, size_t out_index, size_t out_len) {
// We use bitmaps to mark clusters that need binding and config attributes
// All clusters in 'in' and 'out' are bounded
// Only cluster in 'in' receive configure attribute requests
uint32_t cluster_map = 0; // max 32 clusters to bind
uint32_t cluster_in_map = 0; // map of clusters only in 'in' group, to be bounded
// First enumerate all clusters to bind, from in or out clusters
// scan in clusters
for (uint32_t i=0; i<in_len; i++) {
uint16_t cluster = buf.get16(in_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
bitSet(cluster_in_map, found_cx);
}
}
// scan out clusters
for (uint32_t i=0; i<out_len; i++) {
uint16_t cluster = buf.get16(out_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
}
}
// if IAS device, request the device type
if (bitRead(cluster_map, Z_ClusterToCxBinding(0x0500))) {
// send a read command to cluster 0x0500, attribute 0x0001 (ZoneType) - to read the type of sensor
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0500, endpoint, Z_CAT_READ_ATTRIBUTE, 0x0001, &Z_SendSingleAttributeRead);
}
// enqueue bind requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_BIND, 0 /* value */, &Z_AutoBind);
}
}
// enqueue config attribute requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_in_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_CONFIG_ATTR, 0 /* value */, &Z_AutoConfigReportingForCluster);
}
}
}
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
#ifdef USE_ZIGBEE_ZNP
// Received ZDO_SIMPLE_DESC_RSP
// Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t lenDescriptor = buf.get8(7);
uint8_t endpoint = buf.get8(8);
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(13); // 0 Version 1.00
uint8_t numInCluster = buf.get8(14);
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
const size_t numInIndex = 15;
const size_t numOutIndex = 16 + numInCluster*2;
#endif
#ifdef USE_ZIGBEE_EZSP
uint8_t status = buf.get8(0);
Z_ShortAddress nwkAddr = buf.get16(1);
uint8_t lenDescriptor = buf.get8(3);
uint8_t endpoint = buf.get8(4);
uint16_t profileId = buf.get16(5); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(7); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(9); // 0 Version 1.00
uint8_t numInCluster = buf.get8(10);
uint8_t numOutCluster = buf.get8(11 + numInCluster*2);
const size_t numInIndex = 11;
const size_t numOutIndex = 12 + numInCluster*2;
#endif
if (0 == status) {
if (!Settings.flag4.zb_disable_autobind) {
Z_AutoBindDefer(nwkAddr, endpoint, buf, numInIndex, numInCluster, numOutIndex, numOutCluster);
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
"\"InClusters\":["),
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
profileId, deviceId, deviceVersion);
for (uint32_t i = 0; i < numInCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numInIndex + i*2));
}
ResponseAppend_P(PSTR("],\"OutClusters\":["));
for (uint32_t i = 0; i < numOutCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numOutIndex + i*2));
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
}
return -1;
}
@ -831,7 +960,7 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint8_t cluster = buf.get16(idx + 9);
uint16_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
@ -960,20 +1089,215 @@ void Z_SendActiveEpReq(uint16_t shortaddr) {
}
//
// Send AF Info Request
// Probe the clusters_out on the first endpoint
//
void Z_SendAFInfoRequest(uint16_t shortaddr) {
// Send ZDO_SIMPLE_DESC_REQ to get full list of supported Clusters for a specific endpoint
void Z_SendSimpleDescReq(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
#ifdef USE_ZIGBEE_ZNP
uint8_t SimpleDescReq[] = { Z_SREQ | Z_ZDO, ZDO_SIMPLE_DESC_REQ, // 2504
Z_B0(shortaddr), Z_B1(shortaddr), Z_B0(shortaddr), Z_B1(shortaddr),
endpoint };
ZigbeeZNPSend(SimpleDescReq, sizeof(SimpleDescReq));
#endif
#ifdef USE_ZIGBEE_EZSP
uint8_t SimpleDescReq[] = { Z_B0(shortaddr), Z_B1(shortaddr), endpoint };
EZ_SendZDO(shortaddr, ZDO_SIMPLE_DESC_REQ, SimpleDescReq, sizeof(SimpleDescReq));
#endif
}
//
// Send AF Info Request
// Queue requests for the device
// 1. Request for 'ModelId' and 'Manufacturer': 0000/0005, 0000/0006
// 2. Auto-bind to coordinator:
// Iterate among
//
void Z_SendDeviceInfoRequest(uint16_t shortaddr) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (0x00 == endpoint) { endpoint = 0x01; } // if we don't know the endpoint, try 0x01
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
uint8_t InfoReq[] = { 0x04, 0x00, 0x05, 0x00 };
ZigbeeZCLSend_Raw(shortaddr, 0x0000 /*group*/, 0x0000 /*cluster*/, endpoint, ZCL_READ_ATTRIBUTES,
false /*clusterSpecific*/, 0x0000 /*manuf*/,
InfoReq, sizeof(InfoReq), true /*needResponse*/, transacid);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
0x0000 /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
}
//
// Send sing attribute read request in Timer
//
void Z_SendSingleAttributeRead(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
uint8_t transacid = zigbee_devices.getNextSeqNumber(shortaddr);
uint8_t InfoReq[2] = { Z_B0(value), Z_B1(value) }; // list of single attribute
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
cluster /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
}
//
// Auto-bind some clusters to the coordinator's endpoint 0x01
//
void Z_AutoBind(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(shortaddr);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `ZbBind {\"Device\":\"0x%04X\",\"Endpoint\":%d,\"Cluster\":\"0x%04X\"}`"),
shortaddr, endpoint, cluster);
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(34);
buf.add8(Z_SREQ | Z_ZDO);
buf.add8(ZDO_BIND_REQ);
buf.add16(shortaddr);
buf.add64(srcLongAddr);
buf.add8(endpoint);
buf.add16(cluster);
buf.add8(Z_Addr_IEEEAddress); // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf.add64(localIEEEAddr);
buf.add8(0x01); // toEndpoint
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
SBuffer buf(24);
// ZDO message payload (see Zigbee spec 2.4.3.2.2)
buf.add64(srcLongAddr);
buf.add8(endpoint);
buf.add16(cluster);
buf.add8(Z_Addr_IEEEAddress); // DstAddrMode - 0x03 = ADDRESS_64_BIT
buf.add64(localIEEEAddr);
buf.add8(0x01); // toEndpoint
EZ_SendZDO(shortaddr, ZDO_BIND_REQ, buf.buf(), buf.len());
#endif // USE_ZIGBEE_EZSP
}
//
// Auto-bind some clusters to the coordinator's endpoint 0x01
//
// the structure below indicates which attributes need to be configured for attribute reporting
typedef struct Z_autoAttributeReporting_t {
uint16_t cluster;
uint16_t attr_id;
uint16_t min_interval; // minimum interval in seconds (consecutive reports won't happen before this value)
uint16_t max_interval; // maximum interval in seconds (attribut will always be reported after this interval)
float report_change; // for non discrete attributes, the value change that triggers a report
} Z_autoAttributeReporting_t;
// Note the attribute must be registered in the converter list, used to retrieve the type of the attribute
const Z_autoAttributeReporting_t Z_autoAttributeReporting[] PROGMEM = {
{ 0x0001, 0x0020, 15*60, 15*60, 0.1 }, // BatteryVoltage
{ 0x0001, 0x0021, 15*60, 15*60, 1 }, // BatteryPercentage
{ 0x0006, 0x0000, 1, 60*60, 0 }, // Power
{ 0x0008, 0x0000, 1, 60*60, 5 }, // Dimmer
{ 0x0300, 0x0000, 1, 60*60, 5 }, // Hue
{ 0x0300, 0x0001, 1, 60*60, 5 }, // Sat
{ 0x0300, 0x0003, 1, 60*60, 100 }, // X
{ 0x0300, 0x0004, 1, 60*60, 100 }, // Y
{ 0x0300, 0x0007, 1, 60*60, 5 }, // CT
{ 0x0300, 0x0008, 1, 60*60, 0 }, // ColorMode
{ 0x0400, 0x0000, 10, 60*60, 5 }, // Illuminance (5 lux)
{ 0x0402, 0x0000, 30, 60*60, 0.2 }, // Temperature (0.2 °C)
{ 0x0403, 0x0000, 30, 60*60, 1 }, // Pressure (1 hPa)
{ 0x0405, 0x0000, 30, 60*60, 1.0 }, // Humidity (1 %)
{ 0x0406, 0x0000, 10, 60*60, 0 }, // Occupancy
{ 0x0500, 0x0002, 1, 60*60, 0 }, // ZoneStatus
};
//
// Called by Device Auto-config
// Configures default values for the most common Attribute Rerporting configurations
//
// Note: must be of type `Z_DeviceTimer`
void Z_AutoConfigReportingForCluster(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
// Buffer, max 12 bytes per attribute
SBuffer buf(12*6);
Response_P(PSTR("ZbSend {\"Device\":\"0x%04X\",\"Config\":{"), shortaddr);
boolean comma = false;
for (uint32_t i=0; i<ARRAY_SIZE(Z_autoAttributeReporting); i++) {
uint16_t conv_cluster = pgm_read_word(&(Z_autoAttributeReporting[i].cluster));
uint16_t attr_id = pgm_read_word(&(Z_autoAttributeReporting[i].attr_id));
if (conv_cluster == cluster) {
uint16_t min_interval = pgm_read_word(&(Z_autoAttributeReporting[i].min_interval));
uint16_t max_interval = pgm_read_word(&(Z_autoAttributeReporting[i].max_interval));
float report_change_raw = Z_autoAttributeReporting[i].report_change;
double report_change = report_change_raw;
uint8_t attr_type;
int8_t multiplier;
const __FlashStringHelper* attr_name = zigbeeFindAttributeById(cluster, attr_id, &attr_type, &multiplier);
if (attr_name) {
if (comma) { ResponseAppend_P(PSTR(",")); }
comma = true;
ResponseAppend_P(PSTR("\"%s\":{\"MinInterval\":%d,\"MaxInterval\":%d"), attr_name, min_interval, max_interval);
buf.add8(0); // direction, always 0
buf.add16(attr_id);
buf.add8(attr_type);
buf.add16(min_interval);
buf.add16(max_interval);
if (!Z_isDiscreteDataType(attr_type)) { // report_change is only valid for non-discrete data types (numbers)
ZbApplyMultiplier(report_change, multiplier);
// encode value
int32_t res = encodeSingleAttribute(buf, report_change, "", attr_type);
if (res < 0) {
AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "internal error, unsupported attribute type"));
} else {
Z_attribute attr;
attr.setKeyName(PSTR("ReportableChange"), true); // true because in PMEM
attr.setFloat(report_change_raw);
ResponseAppend_P(PSTR(",%s"), attr.toString().c_str());
}
}
ResponseAppend_P(PSTR("}"));
}
}
}
ResponseAppend_P(PSTR("}}"));
if (buf.len() > 0) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "auto-bind `%s`"), mqtt_data);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
cluster /*cluster*/,
endpoint,
ZCL_CONFIGURE_REPORTING,
0x0000, /* manuf */
false /* not cluster specific */,
false /* no response */,
zigbee_devices.getNextSeqNumber(shortaddr), /* zcl transaction id */
buf.buf(), buf.len()
}));
}
}
//
// Handle trustCenterJoinHandler
@ -1011,53 +1335,53 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) {
// Parse incoming ZCL message.
//
// This code is common to ZNP and EZSP
void Z_IncomingMessage(ZCLFrame &zcl_received) {
void Z_IncomingMessage(class ZCLFrame &zcl_received) {
uint16_t srcaddr = zcl_received.getSrcAddr();
uint16_t groupid = zcl_received.getGroupAddr();
uint16_t clusterid = zcl_received.getClusterId();
uint8_t linkquality = zcl_received.getLinkQuality();
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
linkquality = linkquality != 0xFF ? linkquality : 0xFE; // avoid 0xFF (reserved for unknown)
bool defer_attributes = false; // do we defer attributes reporting to coalesce
// log the packet details
zcl_received.log();
zigbee_devices.setLQI(srcaddr, linkquality != 0xFF ? linkquality : 0xFE); // EFR32 has a different scale for LQI
zigbee_devices.setLQI(srcaddr, linkquality); // EFR32 has a different scale for LQI
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
Z_attribute_list attr_list;
attr_list.lqi = linkquality;
attr_list.src_ep = srcendpoint;
if (groupid) { // TODO we miss the group_id == 0 here
attr_list.group_id = groupid;
}
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
zcl_received.parseResponse(); // Zigbee general "Default Response", publish ZbResponse message
} else {
// Build the ZbReceive json
// Build the ZbReceive list
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors
zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributesResponse(json);
zcl_received.parseReadAttributesResponse(attr_list);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json);
zcl_received.parseReadAttributes(attr_list);
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadConfigAttributes(json);
zcl_received.parseReadConfigAttributes(attr_list);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseConfigAttributes(json);
zcl_received.parseConfigAttributes(attr_list);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
zcl_received.parseClusterSpecificCommand(attr_list);
}
{ // fence to force early de-allocation of msg
String msg("");
msg.reserve(100);
json.printTo(msg);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str());
// discard the message if it was sent by us (broadcast or group loopback)
if (srcaddr == localShortAddr) {
@ -1065,37 +1389,25 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
return; // abort the rest of message management
}
zcl_received.postProcessAttributes(srcaddr, json);
// Add Endpoint
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
// Add Group if non-zero
if (groupid) {
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
}
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
zcl_received.generateSyntheticAttributes(attr_list);
zcl_received.generateCallBacks(attr_list); // set deferred callbacks, ex: Occupancy
zcl_received.postProcessAttributes(srcaddr, attr_list);
// since we just receveived data from the device, it is reachable
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
// Post-provess for Aqara Presence Senson
Z_AqaraOccupancy(srcaddr, clusterid, srcendpoint, json);
if (defer_attributes) {
// Prepare for publish
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
if (zigbee_devices.jsonIsConflict(srcaddr, attr_list)) {
// there is conflicting values, force a publish of the previous message now and don't coalesce
zigbee_devices.jsonPublishFlush(srcaddr);
}
zigbee_devices.jsonAppend(srcaddr, json);
zigbee_devices.jsonAppend(srcaddr, attr_list);
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
} else {
// Publish immediately
zigbee_devices.jsonPublishNow(srcaddr, json);
// Add auto-responder here
Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]);
zigbee_devices.jsonPublishNow(srcaddr, attr_list);
}
}
}
@ -1192,6 +1504,8 @@ int32_t EZ_IncomingMessage(int32_t res, const class SBuffer &buf) {
return Z_ReceiveActiveEp(res, zdo_buf);
case ZDO_IEEE_addr_rsp:
return Z_ReceiveIEEEAddr(res, zdo_buf);
case ZDO_Simple_Desc_rsp:
return Z_ReceiveSimpleDesc(res, zdo_buf);
case ZDO_Bind_rsp:
return Z_BindRsp(res, zdo_buf);
case ZDO_Unbind_rsp:
@ -1281,32 +1595,9 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
* Callbacks
\*********************************************************************************************/
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
// Read OCCUPANCY value if any
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(OCCUPANCY));
if (nullptr != &val_endpoint) {
uint32_t occupancy = strToUInt(val_endpoint);
if (occupancy) {
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
} else {
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
}
}
}
// Publish the received values once they have been coalesced
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
if (json == nullptr) { return 0; } // don't crash if not found
void Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
zigbee_devices.jsonPublishFlush(shortaddr);
return 1;
}
/*********************************************************************************************\
@ -1388,6 +1679,7 @@ const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ { Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND }, &ZNP_ReceivePermitJoinStatus }, // 45CB
{ { Z_AREQ | Z_ZDO, ZDO_NODE_DESC_RSP }, &ZNP_ReceiveNodeDesc }, // 4582
{ { Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP }, &Z_ReceiveActiveEp }, // 4585
{ { Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP}, &Z_ReceiveSimpleDesc}, // 4584
{ { Z_AREQ | Z_ZDO, ZDO_IEEE_ADDR_RSP }, &Z_ReceiveIEEEAddr }, // 4581
{ { Z_AREQ | Z_ZDO, ZDO_BIND_RSP }, &Z_BindRsp }, // 45A1
{ { Z_AREQ | Z_ZDO, ZDO_UNBIND_RSP }, &Z_UnbindRsp }, // 45A2
@ -1438,11 +1730,11 @@ void Z_Query_Bulb(uint16_t shortaddr, uint32_t &wait_ms) {
uint8_t endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
if (endpoint) { // send only if we know the endpoint
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0006, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0008, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, wait_ms, 0x0300, endpoint, Z_CAT_READ_CLUSTER, 0 /* value */, &Z_ReadAttrCallback);
wait_ms += inter_message_ms;
zigbee_devices.setTimer(shortaddr, 0, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, 0, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
wait_ms += 1000; // wait 1 second between devices
@ -1476,24 +1768,26 @@ int32_t Z_State_Ready(uint8_t value) {
//
// Mostly used for routers/end-devices
// json: holds the attributes in JSON format
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list, size_t attr_len) {
DynamicJsonBuffer jsonBuffer;
JsonObject& json_out = jsonBuffer.createObject();
// responder
switch (cluster) {
case 0x0000:
if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); }
if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); }
break;
for (uint32_t i=0; i<attr_len; i++) {
uint16_t attr = attr_list[i];
uint32_t ccccaaaa = (cluster << 16) || attr;
switch (ccccaaaa) {
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
#ifdef USE_LIGHT
case 0x0006:
if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; }
break;
case 0x0008:
if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); }
break;
case 0x0300:
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
case 0x03000000: // Hue
case 0x03000001: // Sat
case 0x03000003: // X
case 0x03000004: // Y
case 0x03000007: // CT
{
uint16_t hue;
uint8_t sat;
@ -1505,20 +1799,30 @@ void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const
uxy[i] = XY[i] * 65536.0f;
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
}
if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); }
if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; }
if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; }
if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
if (0x0003 == attr) { json_out[F("X")] = uxy[0]; }
if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; }
if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); }
}
break;
#endif
case 0x000A: // Time
if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeEpoch"))) { json_out[F("TimeEpoch")] = Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized
if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds
case 0x000A0000: // Time
json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time;
break;
case 0x000AFF00: // TimeEpoch - Tasmota specific
json_out[F("TimeEpoch")] = Rtc.utc_time;
break;
case 0x000A0001: // TimeStatus
json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; // if time is beyond 2010 then we are synchronized
break;
case 0x000A0002: // TimeZone
json_out[F("TimeZone")] = Settings.toffset[0] * 60;
break;
case 0x000A0007: // LocalTime // TODO take DST
json_out[F("LocalTime")] = Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time);
break;
}
}
if (json_out.size() > 0) {

View File

@ -598,6 +598,7 @@ int32_t ZigbeeProcessInputEZSP(class SBuffer &buf) {
case EZSP_messageSentHandler: // 3F00
case EZSP_setConfigurationValue: // 5300
case EZSP_setPolicy: // 5500
case 0x0059: // 5900 - supposedly removed by still happening
case EZSP_setMulticastTableEntry: // 6400
case EZSP_setInitialSecurityState: // 6800
case EZSP_getCurrentSecurityState: // 6900
@ -765,96 +766,96 @@ void CmndZbEZSPSend(void)
// - transacId: 8-bits, transation id of message (should be incremented at each message), used both for Zigbee message number and ZCL message number
// Returns: None
//
void ZigbeeZCLSend_Raw(uint16_t shortaddr, uint16_t groupaddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, uint16_t manuf, const uint8_t *msg, size_t len, bool needResponse, uint8_t transacId) {
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl) {
#ifdef USE_ZIGBEE_ZNP
SBuffer buf(32+len);
SBuffer buf(32+zcl.len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST_EXT); // 02
if (BAD_SHORTADDR == shortaddr) { // if no shortaddr we assume group address
if (BAD_SHORTADDR == zcl.shortaddr) { // if no shortaddr we assume group address
buf.add8(Z_Addr_Group); // 01
buf.add64(groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add64(zcl.groupaddr); // group address, only 2 LSB, upper 6 MSB are discarded
buf.add8(0xFF); // dest endpoint is not used for group addresses
} else {
buf.add8(Z_Addr_ShortAddress); // 02
buf.add64(shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded
buf.add8(endpoint); // dest endpoint
buf.add64(zcl.shortaddr); // dest address, only 2 LSB, upper 6 MSB are discarded
buf.add8(zcl.endpoint); // dest endpoint
}
buf.add16(0x0000); // dest Pan ID, 0x0000 = intra-pan
buf.add8(0x01); // source endpoint
buf.add16(clusterId);
buf.add8(transacId); // transacId
buf.add16(zcl.clusterId);
buf.add8(zcl.transacId); // transacId
buf.add8(0x30); // 30 options
buf.add8(0x1E); // 1E radius
buf.add16(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
buf.add16(3 + zcl.len + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequence Number
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
buf.add8(zcl.transacId); // Transaction Sequence Number
buf.add8(zcl.cmdId);
if (zcl.len > 0) {
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
ZigbeeZNPSend(buf.getBuffer(), buf.len());
#endif // USE_ZIGBEE_ZNP
#ifdef USE_ZIGBEE_EZSP
SBuffer buf(32+len);
SBuffer buf(32+zcl.len);
if (BAD_SHORTADDR != shortaddr) {
if (BAD_SHORTADDR != zcl.shortaddr) {
// send unicast message to an address
buf.add16(EZSP_sendUnicast); // 3400
buf.add8(EMBER_OUTGOING_DIRECT); // 00
buf.add16(shortaddr); // dest addr
buf.add16(zcl.shortaddr); // dest addr
// ApsFrame
buf.add16(Z_PROF_HA); // Home Automation profile
buf.add16(clusterId); // cluster
buf.add16(zcl.clusterId); // cluster
buf.add8(0x01); // srcEp
buf.add8(endpoint); // dstEp
buf.add8(zcl.endpoint); // dstEp
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
buf.add16(groupaddr); // groupId
buf.add8(transacId);
buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transacId);
// end of ApsFrame
buf.add8(0x01); // tag TODO
buf.add8(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
buf.add8(3 + zcl.len + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
buf.add8(zcl.transacId); // Transaction Sequance Number
buf.add8(zcl.cmdId);
if (zcl.len > 0) {
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
} else {
// send broadcast group address, aka groupcast
buf.add16(EZSP_sendMulticast); // 3800
// ApsFrame
buf.add16(Z_PROF_HA); // Home Automation profile
buf.add16(clusterId); // cluster
buf.add16(zcl.clusterId); // cluster
buf.add8(0x01); // srcEp
buf.add8(endpoint); // broadcast endpoint for groupcast
buf.add8(zcl.endpoint); // broadcast endpoint for groupcast
buf.add16(EMBER_APS_OPTION_ENABLE_ROUTE_DISCOVERY | EMBER_APS_OPTION_RETRY); // APS frame
buf.add16(groupaddr); // groupId
buf.add8(transacId);
buf.add16(zcl.groupaddr); // groupId
buf.add8(zcl.transacId);
// end of ApsFrame
buf.add8(0); // hops, 0x00 = EMBER_MAX_HOPS
buf.add8(7); // nonMemberRadius, 7 = infinite
buf.add8(0x01); // tag TODO
buf.add8(3 + len + (manuf ? 2 : 0));
buf.add8((needResponse ? 0x00 : 0x10) | (clusterSpecific ? 0x01 : 0x00) | (manuf ? 0x04 : 0x00)); // Frame Control Field
if (manuf) {
buf.add16(manuf); // add Manuf Id if not null
buf.add8(3 + zcl.len + (zcl.manuf ? 2 : 0));
buf.add8((zcl.needResponse ? 0x00 : 0x10) | (zcl.clusterSpecific ? 0x01 : 0x00) | (zcl.manuf ? 0x04 : 0x00)); // Frame Control Field
if (zcl.manuf) {
buf.add16(zcl.manuf); // add Manuf Id if not null
}
buf.add8(transacId); // Transaction Sequance Number
buf.add8(cmdId);
if (len > 0) {
buf.addBuffer(msg, len); // add the payload
buf.add8(zcl.transacId); // Transaction Sequance Number
buf.add8(zcl.cmdId);
if (zcl.len > 0) {
buf.addBuffer(zcl.msg, zcl.len); // add the payload
}
}

View File

@ -139,7 +139,6 @@ void CmndZbReset(void) {
// High-level function
// Send a command specified as an HEX string for the workload.
// The target endpoint is computed if zero, i.e. sent to the first known endpoint of the device.
// If cluster-specific, a timer may be set calling `zigbeeSetCommandTimer()`, for ex to coalesce attributes or Aqara presence sensor
//
// Inputs:
// - shortaddr: 16-bits short address, or 0x0000 if group address
@ -176,11 +175,24 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint,
}
// everything is good, we can send the command
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, cmd, clusterSpecific, manuf, buf.getBuffer(), buf.len(), true, zigbee_devices.getNextSeqNumber(shortaddr));
uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
groupaddr,
cluster /*cluster*/,
endpoint,
cmd,
manuf, /* manuf */
clusterSpecific /* not cluster specific */,
true /* response */,
seq, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
// now set the timer, if any, to read back the state later
if (clusterSpecific) {
#ifndef USE_ZIGBEE_NO_READ_ATTRIBUTES // read back attribute value unless it is disabled
zigbeeSetCommandTimer(shortaddr, groupaddr, cluster, endpoint);
sendHueUpdate(shortaddr, groupaddr, cluster, endpoint);
#endif
}
}
@ -202,7 +214,7 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
// Parse "Report", "Write", "Response" or "Condig" attribute
// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01)
void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) {
void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
SBuffer buf(200); // buffer to store the binary output of attibutes
if (nullptr == XdrvMailbox.command) {
@ -376,7 +388,19 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
}
// all good, send the packet
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, false /* not cluster specific */, manuf, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(device));
uint8_t seq = zigbee_devices.getNextSeqNumber(device);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
device,
groupaddr,
cluster /*cluster*/,
endpoint,
operation,
manuf, /* manuf */
false /* not cluster specific */,
false /* no response */,
seq, /* zcl transaction id */
buf.getBuffer(), buf.len()
}));
ResponseCmndDone();
}
@ -510,7 +534,7 @@ void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr,
// Parse the "Send" attribute and send the command
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) {
void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint8_t operation) {
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5}
// ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"}
// ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]}
@ -602,7 +626,19 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
}
if (attrs_len > 0) {
ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device));
uint8_t seq = zigbee_devices.getNextSeqNumber(device);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
device,
groupaddr,
cluster /*cluster*/,
endpoint,
operation,
manuf, /* manuf */
false /* not cluster specific */,
true /* response */,
seq, /* zcl transaction id */
attrs, attrs_len
}));
ResponseCmndDone();
} else {
ResponseCmndChar_P(PSTR("Missing parameters"));
@ -795,7 +831,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
if (nullptr != &val_cluster) {
cluster = strToUInt(val_cluster); // first convert as number
if (0 == cluster) {
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr, nullptr);
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -52,7 +52,7 @@ AudioFileSourceFS *file;
AudioOutputI2S *out;
AudioFileSourceID3 *id3;
AudioGeneratorMP3 *decoder = NULL;
void *mp3ram = NULL;
#ifdef USE_WEBRADIO
AudioFileSourceICYStream *ifile = NULL;
@ -210,6 +210,12 @@ void I2S_Init(void) {
is2_volume=10;
out->SetGain(((float)is2_volume/100.0)*4.0);
out->stop();
mp3ram = nullptr;
#ifdef ESP32
if (psramFound()) {
mp3ram = heap_caps_malloc(preallocateCodecSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
}
#ifdef USE_WEBRADIO
if (psramFound()) {
@ -223,6 +229,7 @@ void I2S_Init(void) {
//Serial.printf_P(PSTR("FATAL ERROR: Unable to preallocate %d bytes for app\n"), preallocateBufferSize+preallocateCodecSize);
}
#endif // USE_WEBRADIO
#endif // ESP32
}
#ifdef ESP32
@ -285,7 +292,7 @@ void Webradio(const char *url) {
retryms = millis() + 2000;
}
xTaskCreatePinnedToCore(mp3_task2, "MP3", 8192, NULL, 3, &mp3_task_h, 1);
xTaskCreatePinnedToCore(mp3_task2, "MP3-2", 8192, NULL, 3, &mp3_task_h, 1);
}
void mp3_task2(void *arg){
@ -366,7 +373,12 @@ void Play_mp3(const char *path) {
file = new AudioFileSourceFS(*fsp,path);
id3 = new AudioFileSourceID3(file);
if (mp3ram) {
mp3 = new AudioGeneratorMP3(mp3ram, preallocateCodecSize);
} else {
mp3 = new AudioGeneratorMP3();
}
mp3->begin(id3, out);
if (I2S_Task) {

View File

@ -205,10 +205,10 @@ void HlwEverySecond(void)
unsigned long hlw_len;
if (Hlw.energy_period_counter) {
hlw_len = 10000 * 1000 / Hlw.energy_period_counter; // Add *1000 to fix rounding on loads at 3.6kW (#9160)
hlw_len = 10000 * 100 / Hlw.energy_period_counter; // Add *100 to fix rounding on loads at 3.6kW (#9160)
Hlw.energy_period_counter = 0;
if (hlw_len) {
Energy.kWhtoday_delta += ((Hlw.power_ratio * Settings.energy_power_calibration) * 1000 / hlw_len) / 36;
Energy.kWhtoday_delta += (((Hlw.power_ratio * Settings.energy_power_calibration) / 36) * 100) / hlw_len;
EnergyUpdateToday();
}
}

View File

@ -155,8 +155,14 @@ a_setoption = [[
"Enable zerocross dimmer on PWM DIMMER",
"Remove ZbReceived form JSON message",
"Add the source endpoint as suffix to attributes",
"","","","",
"","","","",
"Baud rate for Teleinfo communication (0 = 1200 or 1 = 9600)",
"TLS mode",
"Disable all MQTT retained messages",
"Enable White blend mode",
"Create a virtual White ColorTemp for RGBW lights",
"Select virtual White as (0) Warm or (1) Cold",
"Enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1)",
"Force gen1 Alexa mode",
"","","",""
],[
"","","","",
@ -220,9 +226,9 @@ a_features = [[
"USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075",
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_MLX90640",
"","","","",
"","","USE_ETHERNET","USE_WEBCAM"
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_I2S_AUDIO",
"USE_MLX90640","","","",
"","USE_TTGO_WATCH","USE_ETHERNET","USE_WEBCAM"
],[
"","","","",
"","","","",
@ -259,7 +265,7 @@ else:
obj = json.load(fp)
def StartDecode():
print ("\n*** decode-status.py v20200817 by Theo Arends and Jacek Ziolkowski ***")
print ("\n*** decode-status.py v20200906 by Theo Arends and Jacek Ziolkowski ***")
# print("Decoding\n{}".format(obj))