mirror of https://github.com/arendst/Tasmota.git
update to newest version
This commit is contained in:
commit
1b56a97298
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
10
BUILDS.md
10
BUILDS.md
|
@ -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 | - | - | - | - | - | - | - | - |
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
452
TEMPLATES.md
452
TEMPLATES.md
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -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
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
Loading…
Reference in New Issue