mirror of https://github.com/arendst/Tasmota.git
Merge remote-tracking branch 'remotes/upstream/development' into development
This commit is contained in:
commit
51688c18ea
|
@ -17,8 +17,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32
|
||||
|
||||
tasmota32-webcam:
|
||||
|
@ -34,8 +33,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-webcam
|
||||
|
||||
tasmota32-minimal:
|
||||
|
@ -51,8 +49,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-minimal
|
||||
tasmota32-lite:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -67,8 +64,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-lite
|
||||
tasmota32-knx:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -83,8 +79,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-knx
|
||||
|
||||
tasmota32-sensors:
|
||||
|
@ -100,8 +95,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-sensors
|
||||
tasmota32-display:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -116,8 +110,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-display
|
||||
tasmota32-ir:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -132,8 +125,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-ir
|
||||
tasmota32-BG:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -148,8 +140,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-BG
|
||||
|
||||
tasmota32-BR:
|
||||
|
@ -165,8 +156,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-BR
|
||||
tasmota32-CN:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -181,8 +171,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-CN
|
||||
tasmota32-CZ:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -197,8 +186,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-CZ
|
||||
tasmota32-DE:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -213,8 +201,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-DE
|
||||
|
||||
tasmota32-ES:
|
||||
|
@ -230,8 +217,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-ES
|
||||
tasmota32-FR:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -246,8 +232,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-FR
|
||||
tasmota32-GR:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -262,8 +247,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-GR
|
||||
tasmota32-HE:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -278,8 +262,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-HE
|
||||
|
||||
tasmota32-HU:
|
||||
|
@ -295,8 +278,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-HU
|
||||
tasmota32-IT:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -311,8 +293,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-IT
|
||||
tasmota32-KO:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -327,8 +308,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-KO
|
||||
tasmota32-NL:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -343,8 +323,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-NL
|
||||
|
||||
tasmota32-PL:
|
||||
|
@ -360,8 +339,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-PL
|
||||
tasmota32-PT:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -376,8 +354,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-PT
|
||||
tasmota32-RO:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -392,8 +369,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-RO
|
||||
tasmota32-RU:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -408,8 +384,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-RU
|
||||
|
||||
tasmota32-SE:
|
||||
|
@ -425,8 +400,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-SE
|
||||
tasmota32-SK:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -441,8 +415,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-SK
|
||||
tasmota32-TR:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -457,8 +430,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-TR
|
||||
tasmota32-TW:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -473,8 +445,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-TW
|
||||
|
||||
tasmota32-UK:
|
||||
|
@ -490,6 +461,5 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-UK
|
||||
|
|
|
@ -750,8 +750,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -774,8 +773,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-minimal
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -798,8 +796,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-lite
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -822,8 +819,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-webcam
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -846,8 +842,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-knx
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -870,8 +865,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-sensors
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -894,8 +888,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-display
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -918,8 +911,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-ir
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -942,8 +934,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-ircustom
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -966,8 +957,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-BG
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -990,8 +980,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-BR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1014,8 +1003,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-CN
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1038,8 +1026,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-CZ
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1062,8 +1049,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-DE
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1086,8 +1072,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-ES
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1110,8 +1095,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-FR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1134,8 +1118,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-GR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1158,8 +1141,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-HE
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1182,8 +1164,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-HU
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1206,8 +1187,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-IT
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1230,8 +1210,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-KO
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1254,8 +1233,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-NL
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1278,8 +1256,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-PL
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1302,8 +1279,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-PT
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1326,8 +1302,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-RO
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1350,8 +1325,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-RU
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1374,8 +1348,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-SE
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1398,8 +1371,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-SK
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1422,8 +1394,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-TR
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1446,8 +1417,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-TW
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -1470,8 +1440,7 @@ jobs:
|
|||
platformio upgrade --dev
|
||||
platformio update
|
||||
- name: Run PlatformIO
|
||||
run: |
|
||||
mv -f platformio_override_sample.ini platformio_override.ini
|
||||
run: |
|
||||
platformio run -e tasmota32-UK
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
|
|
@ -74,4 +74,5 @@ Index | Define | Driver | Device | Address(es) | Description
|
|||
49 | USE_VEML6075 | xsns_70 | VEML6075 | 0x10 | UVA/UVB/UVINDEX Sensor
|
||||
50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor
|
||||
51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor
|
||||
52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor
|
||||
53 | USE_MLX90640 | xdrv_84 | MLX90640 | 0x33 | IR array temperature sensor
|
|
@ -47,7 +47,7 @@ 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``
|
||||
The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota/release for ESP8266 or http://ota.tasmota.com/tasmota32/release for ESP32. The links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmota.com/tasmota/release/tasmota.bin``
|
||||
|
||||
[List](MODULES.md) of embedded modules.
|
||||
|
||||
|
@ -59,5 +59,8 @@ Binaries for ESP8266 based devices can be downloaded from http://ota.tasmota.com
|
|||
|
||||
- Fix energy total counters (#9263, #9266)
|
||||
- Fix crash in ``ZbRestore``
|
||||
- Fix reset BMP sensors when executing command ``SaveData`` and define USE_DEEPSLEEP enabled (#9300)
|
||||
- Add new shutter modes (#9244)
|
||||
- Add ``#define USE_MQTT_AWS_IOT_LIGHT`` for password based AWS IoT authentication
|
||||
- Add Zigbee auto-config when pairing
|
||||
- Add support for MLX90640 IR array temperature sensor by Christian Baars
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* @copyright (C) 2017 Melexis N.V.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
#ifndef _MLX90640_API_H_
|
||||
#define _MLX90640_API_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SCALEALPHA 0.000001
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int16_t kVdd;
|
||||
int16_t vdd25;
|
||||
float KvPTAT;
|
||||
float KtPTAT;
|
||||
uint16_t vPTAT25;
|
||||
float alphaPTAT;
|
||||
int16_t gainEE;
|
||||
float tgc;
|
||||
float cpKv;
|
||||
float cpKta;
|
||||
uint8_t resolutionEE;
|
||||
uint8_t calibrationModeEE;
|
||||
float KsTa;
|
||||
float ksTo[5];
|
||||
int16_t ct[5];
|
||||
uint16_t alpha[768];
|
||||
uint8_t alphaScale;
|
||||
int16_t offset[768];
|
||||
int8_t kta[768];
|
||||
uint8_t ktaScale;
|
||||
int8_t kv[768];
|
||||
uint8_t kvScale;
|
||||
float cpAlpha[2];
|
||||
int16_t cpOffset[2];
|
||||
float ilChessC[3];
|
||||
uint16_t brokenPixels[5];
|
||||
uint16_t outlierPixels[5];
|
||||
} paramsMLX90640;
|
||||
|
||||
int MLX90640_DumpEE(uint8_t slaveAddr, uint16_t *eeData);
|
||||
int MLX90640_SynchFrame(uint8_t slaveAddr);
|
||||
// int MLX90640_TriggerMeasurement(uint8_t slaveAddr);
|
||||
int MLX90640_GetFrameData(uint8_t slaveAddr, uint16_t *frameData);
|
||||
int MLX90640_ExtractParameters(uint16_t *eeData, paramsMLX90640 *mlx90640,int _chunk);
|
||||
float MLX90640_GetVdd(uint16_t *frameData, const paramsMLX90640 *params);
|
||||
float MLX90640_GetTa(uint16_t *frameData, const paramsMLX90640 *params);
|
||||
// void MLX90640_GetImage(uint16_t *frameData, const paramsMLX90640 *params, float *result);
|
||||
void MLX90640_CalculateTo(uint16_t *frameData, const paramsMLX90640 *params, float emissivity, float tr, float *result, uint8_t _part);
|
||||
int MLX90640_SetResolution(uint8_t slaveAddr, uint8_t resolution);
|
||||
int MLX90640_GetCurResolution(uint8_t slaveAddr);
|
||||
int MLX90640_SetRefreshRate(uint8_t slaveAddr, uint8_t refreshRate);
|
||||
int MLX90640_GetRefreshRate(uint8_t slaveAddr);
|
||||
int MLX90640_GetSubPageNumber(uint16_t *frameData);
|
||||
int MLX90640_GetCurMode(uint8_t slaveAddr);
|
||||
int MLX90640_SetInterleavedMode(uint8_t slaveAddr);
|
||||
int MLX90640_SetChessMode(uint8_t slaveAddr);
|
||||
void MLX90640_BadPixelsCorrection(uint16_t *pixels, float *to, int mode, paramsMLX90640 *params);
|
||||
|
||||
#endif
|
|
@ -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,18 +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_tasmota32.ini
|
||||
platformio_tasmota_env.ini
|
||||
platformio_tasmota_env32.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
|
||||
|
@ -53,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
|
||||
|
|
|
@ -1,6 +1,44 @@
|
|||
; *** 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
|
||||
|
|
|
@ -6,8 +6,11 @@
|
|||
|
||||
- Fix energy total counters (#9263, #9266)
|
||||
- Fix crash in ``ZbRestore``
|
||||
- Fix reset BMP sensors when executing command ``SaveData`` and define USE_DEEPSLEEP enabled (#9300)
|
||||
- Add new shutter modes (#9244)
|
||||
- Add ``#define USE_MQTT_AWS_IOT_LIGHT`` for password based AWS IoT authentication
|
||||
- Add Zigbee auto-config when pairing
|
||||
- Add support for MLX90640 IR array temperature sensor by Christian Baars
|
||||
|
||||
### 8.5.0 20200907
|
||||
|
||||
|
|
|
@ -874,10 +874,8 @@ extern "C" {
|
|||
// 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); // 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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -558,6 +558,7 @@
|
|||
// #define USE_VEML7700 // [I2cDriver50] Enable VEML7700 Ambient Light sensor (I2C addresses 0x10) (+4k5 code)
|
||||
// #define USE_MCP9808 // [I2cDriver51] Enable MCP9808 temperature sensor (I2C addresses 0x18 - 0x1F) (+0k9 code)
|
||||
// #define USE_HP303B // [I2cDriver52] Enable HP303B temperature and pressure sensor (I2C address 0x76 or 0x77) (+6k2 code)
|
||||
// #define USE_MLX90640 // [I2cDriver53] Enable MLX90640 IR array temperature sensor (I2C address 0x33) (+20k code)
|
||||
|
||||
// #define USE_DISPLAY // Add I2C Display Support (+2k code)
|
||||
#define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0
|
||||
|
|
|
@ -129,8 +129,8 @@ 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 buzzer_freq_mode : 1; // bit 28 (v8.4.0.3) - SetOption110 - SetOption110 - Use frequency output for buzzer pin instead of on/off signal
|
||||
uint32_t spare29 : 1; // bit 29
|
||||
uint32_t zb_disable_autobind : 1; // bit 28 (v8.5.0.1) - SetOption110 - disable Zigbee auto-config when pairing new devices
|
||||
uint32_t buzzer_freq_mode : 1; // bit 29 (v8.5.0.1) - SetOption111 - Use frequency output for buzzer pin instead of on/off signal
|
||||
uint32_t spare30 : 1; // bit 30
|
||||
uint32_t spare31 : 1; // bit 31
|
||||
};
|
||||
|
|
|
@ -2006,3 +2006,27 @@ String Decompress(const char * compressed, size_t uncompressed_size) {
|
|||
}
|
||||
|
||||
#endif // USE_UNISHOX_COMPRESSION
|
||||
|
||||
/*********************************************************************************************\
|
||||
* High entropy hardware random generator
|
||||
* Thanks to DigitalAlchemist
|
||||
\*********************************************************************************************/
|
||||
// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c
|
||||
uint32_t HwRandom(void) {
|
||||
#if ESP8266
|
||||
// https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator
|
||||
#define _RAND_ADDR 0x3FF20E44UL
|
||||
#else // ESP32
|
||||
#define _RAND_ADDR 0x3FF75144UL
|
||||
#endif
|
||||
static uint32_t last_ccount = 0;
|
||||
uint32_t ccount;
|
||||
uint32_t result = 0;
|
||||
do {
|
||||
ccount = ESP.getCycleCount();
|
||||
result ^= *(volatile uint32_t *)_RAND_ADDR;
|
||||
} while (ccount - last_ccount < 64);
|
||||
last_ccount = ccount;
|
||||
return result ^ *(volatile uint32_t *)_RAND_ADDR;
|
||||
#undef _RAND_ADDR
|
||||
}
|
||||
|
|
|
@ -601,8 +601,9 @@ void GetFeatures(void)
|
|||
#ifdef USE_I2S_AUDIO
|
||||
feature6 |= 0x00800000; // xdrv_42_i2s_audio.ino
|
||||
#endif
|
||||
|
||||
// feature6 |= 0x01000000;
|
||||
#ifdef USE_MLX90640
|
||||
feature6 |= 0x01000000; // xdrv_43_mlx90640.ino
|
||||
#endif
|
||||
// feature6 |= 0x02000000;
|
||||
// feature6 |= 0x04000000;
|
||||
// feature6 |= 0x08000000;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -57,7 +57,7 @@ struct MQTT {
|
|||
uint8_t initial_connection_state = 2; // MQTT connection messages state
|
||||
bool connected = false; // MQTT virtual connection status
|
||||
bool allowed = false; // MQTT enabled and parameters valid
|
||||
bool tls_private_key = false; // MQTT require a private key before connecting
|
||||
bool mqtt_tls = false; // MQTT TLS is enabled
|
||||
} Mqtt;
|
||||
|
||||
#ifdef USE_MQTT_TLS
|
||||
|
@ -149,22 +149,24 @@ void MqttInit(void)
|
|||
// Turn on TLS for port 8883 (TLS) and 8884 (TLS, client certificate)
|
||||
Settings.flag4.mqtt_tls = true;
|
||||
}
|
||||
Mqtt.mqtt_tls = Settings.flag4.mqtt_tls; // this flag should not change even if we change the SetOption (until reboot)
|
||||
|
||||
// Detect AWS IoT and set default parameters
|
||||
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;
|
||||
}
|
||||
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
|
||||
|
||||
#ifdef USE_MQTT_AWS_IOT
|
||||
loadTlsDir(); // load key and certificate data from Flash
|
||||
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
||||
AWS_IoT_Private_Key,
|
||||
0xFFFF /* all usages, don't care */, 0);
|
||||
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
|
||||
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
||||
AWS_IoT_Private_Key,
|
||||
0xFFFF /* all usages, don't care */, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_MQTT_TLS_CA_CERT
|
||||
|
@ -578,8 +580,8 @@ void MqttReconnect(void)
|
|||
}
|
||||
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
||||
// don't enable MQTT for AWS IoT if Private Key or Certificate are not set
|
||||
if (Settings.flag4.mqtt_tls && Mqtt.tls_private_key) {
|
||||
if (!AWS_IoT_Private_Key || !AWS_IoT_Client_Certificate) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
if (0 == strlen(SettingsText(SET_MQTT_PWD))) { // we anticipate that an empty password does not make sense with TLS. This avoids failed connections
|
||||
Mqtt.allowed = false;
|
||||
}
|
||||
}
|
||||
|
@ -614,7 +616,7 @@ void MqttReconnect(void)
|
|||
|
||||
if (MqttClient.connected()) { MqttClient.disconnect(); }
|
||||
#ifdef USE_MQTT_TLS
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
tlsClient->stop();
|
||||
} else {
|
||||
EspClient = WiFiClient(); // Wifi Client reconnect issue 4497 (https://github.com/esp8266/Arduino/issues/4497)
|
||||
|
@ -632,10 +634,12 @@ void MqttReconnect(void)
|
|||
MqttClient.setCallback(MqttDataHandler);
|
||||
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
||||
// re-assign private keys in case it was updated in between
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
||||
AWS_IoT_Private_Key,
|
||||
0xFFFF /* all usages, don't care */, 0);
|
||||
if (Mqtt.mqtt_tls) {
|
||||
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
|
||||
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
|
||||
AWS_IoT_Private_Key,
|
||||
0xFFFF /* all usages, don't care */, 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
|
||||
|
@ -645,7 +649,7 @@ void MqttReconnect(void)
|
|||
bool allow_all_fingerprints;
|
||||
bool learn_fingerprint1;
|
||||
bool learn_fingerprint2;
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
allow_all_fingerprints = false;
|
||||
learn_fingerprint1 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[0], 0x00);
|
||||
learn_fingerprint2 = is_fingerprint_mono_value(Settings.mqtt_fingerprint[1], 0x00);
|
||||
|
@ -658,16 +662,18 @@ void MqttReconnect(void)
|
|||
#endif
|
||||
bool lwt_retain = Settings.flag4.mqtt_no_retain ? false : true; // no retained last will if "no_retain"
|
||||
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
|
||||
if (Settings.flag4.mqtt_tls && Mqtt.tls_private_key) {
|
||||
// If we require private key then we should null user/pwd
|
||||
mqtt_user = nullptr;
|
||||
mqtt_pwd = nullptr;
|
||||
if (Mqtt.mqtt_tls) {
|
||||
if ((nullptr != AWS_IoT_Private_Key) && (nullptr != AWS_IoT_Client_Certificate)) {
|
||||
// if private key is there, we remove user/pwd
|
||||
mqtt_user = nullptr;
|
||||
mqtt_pwd = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, lwt_retain, mqtt_data, MQTT_CLEAN_SESSION)) {
|
||||
#ifdef USE_MQTT_TLS
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, max ThunkStack used %d"),
|
||||
millis() - mqtt_connect_time, tlsClient->getMaxThunkStackUse());
|
||||
if (!tlsClient->getMFLNStatus()) {
|
||||
|
@ -739,7 +745,7 @@ void MqttReconnect(void)
|
|||
MqttConnected();
|
||||
} else {
|
||||
#ifdef USE_MQTT_TLS
|
||||
if (Settings.flag4.mqtt_tls) {
|
||||
if (Mqtt.mqtt_tls) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connection error: %d"), tlsClient->getLastError());
|
||||
}
|
||||
#endif
|
||||
|
@ -1311,7 +1317,7 @@ void HandleMqttConfiguration(void)
|
|||
SettingsText(SET_MQTT_HOST),
|
||||
Settings.mqtt_port,
|
||||
#ifdef USE_MQTT_TLS
|
||||
Settings.flag4.mqtt_tls ? " checked" : "", // SetOption102 - Enable MQTT TLS
|
||||
Mqtt.mqtt_tls ? " checked" : "", // SetOption102 - Enable MQTT TLS
|
||||
#endif // USE_MQTT_TLS
|
||||
Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT));
|
||||
WSContentSend_P(HTTP_FORM_MQTT2,
|
||||
|
@ -1346,7 +1352,7 @@ void MqttSaveSettings(void)
|
|||
WebGetArg("ml", tmp, sizeof(tmp));
|
||||
Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp);
|
||||
#ifdef USE_MQTT_TLS
|
||||
Settings.flag4.mqtt_tls = Webserver->hasArg("b3"); // SetOption102 - Enable MQTT TLS
|
||||
Mqtt.mqtt_tls = Webserver->hasArg("b3"); // SetOption102 - Enable MQTT TLS
|
||||
#endif
|
||||
WebGetArg("mc", tmp, sizeof(tmp));
|
||||
SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
};
|
||||
|
||||
void ZigbeeZCLSend_Raw(const ZigbeeZCLSendMessage &zcl);
|
||||
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok = false);
|
||||
|
||||
// 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) {
|
||||
|
|
|
@ -101,11 +101,13 @@ public:
|
|||
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)
|
||||
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)
|
||||
uint8_t attr_type; // [opt] type of the attribute, default to Zunk (0xFF)
|
||||
uint8_t attr_multiplier; // [opt] multiplier for attribute, defaults to 0x01 (no change)
|
||||
|
||||
// Constructor with all defaults
|
||||
Z_attribute():
|
||||
|
@ -115,7 +117,9 @@ public:
|
|||
key_is_str(false),
|
||||
key_is_pmem(false),
|
||||
val_str_raw(false),
|
||||
key_suffix(1)
|
||||
key_suffix(1),
|
||||
attr_type(0xFF),
|
||||
attr_multiplier(1)
|
||||
{};
|
||||
|
||||
Z_attribute(const Z_attribute & rhs) {
|
||||
|
@ -247,6 +251,7 @@ public:
|
|||
}
|
||||
|
||||
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
|
||||
inline bool isNone(void) const { return (type == Za_type::Za_none);}
|
||||
// get num values
|
||||
float getFloat(void) const {
|
||||
switch (type) {
|
||||
|
@ -483,6 +488,8 @@ protected:
|
|||
key_is_str = rhs.key_is_str;
|
||||
key_is_pmem = rhs.key_is_pmem;
|
||||
key_suffix = rhs.key_suffix;
|
||||
attr_type = rhs.attr_type;
|
||||
attr_multiplier = rhs.attr_multiplier;
|
||||
// copy value
|
||||
copyVal(rhs);
|
||||
// don't touch next pointer
|
||||
|
|
|
@ -36,6 +36,10 @@ 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
|
||||
// Used for attribute reporting
|
||||
Z_attribute_list attr_list;
|
||||
|
@ -78,6 +82,7 @@ public:
|
|||
manufacturerId(nullptr),
|
||||
modelId(nullptr),
|
||||
friendlyName(nullptr),
|
||||
defer_last_message_sent(0),
|
||||
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||
attr_list(),
|
||||
shortaddr(_shortaddr),
|
||||
|
@ -145,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
|
||||
|
@ -258,8 +270,9 @@ 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
|
||||
|
@ -723,12 +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 & defer : _deferred) {
|
||||
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
|
||||
if ((0xFF == category) || (defer.category == category)) {
|
||||
_deferred.remove(&defer);
|
||||
if ((0xFFFF == cluster) || (defer.cluster == cluster)) {
|
||||
if ((0xFF == endpoint) || (defer.endpoint == endpoint)) {
|
||||
_deferred.remove(&defer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -737,8 +755,8 @@ 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
|
||||
|
@ -753,6 +771,21 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
|
|||
func };
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
@ -918,44 +951,43 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know
|
|||
|
||||
// Display the tracked status for a light
|
||||
String Z_Devices::dumpLightState(uint16_t shortaddr) const {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json = jsonBuffer.createObject();
|
||||
Z_attribute_list attr_list;
|
||||
char hex[8];
|
||||
|
||||
const Z_Device & device = findShortAddr(shortaddr);
|
||||
if (foundDevice(device)) {
|
||||
const char * fname = getFriendlyName(shortaddr);
|
||||
const char * fname = getFriendlyName(shortaddr);
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
|
||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||
|
||||
snprintf_P(hex, sizeof(hex), PSTR("0x%04X"), shortaddr);
|
||||
|
||||
JsonObject& dev = use_fname ? json.createNestedObject((char*) fname) // casting (char*) forces a copy
|
||||
: json.createNestedObject(hex);
|
||||
if (use_fname) {
|
||||
dev[F(D_JSON_ZIGBEE_DEVICE)] = hex;
|
||||
} else if (fname) {
|
||||
dev[F(D_JSON_ZIGBEE_NAME)] = (char*) fname;
|
||||
}
|
||||
|
||||
// expose the last known status of the bulb, for Hue integration
|
||||
dev[F(D_JSON_ZIGBEE_LIGHT)] = getHueBulbtype(shortaddr); // sign extend, 0xFF changed as -1
|
||||
// dump all known values
|
||||
dev[F("Reachable")] = device.getReachable(); // TODO TODO
|
||||
if (device.validPower()) { dev[F("Power")] = device.getPower(); }
|
||||
if (device.validDimmer()) { dev[F("Dimmer")] = device.dimmer; }
|
||||
if (device.validColormode()) { dev[F("Colormode")] = device.colormode; }
|
||||
if (device.validCT()) { dev[F("CT")] = device.ct; }
|
||||
if (device.validSat()) { dev[F("Sat")] = device.sat; }
|
||||
if (device.validHue()) { dev[F("Hue")] = device.hue; }
|
||||
if (device.validX()) { dev[F("X")] = device.x; }
|
||||
if (device.validY()) { dev[F("Y")] = device.y; }
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_DEVICE)).setStr(hex);
|
||||
if (fname) {
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_NAME)).setStr(fname);
|
||||
}
|
||||
|
||||
String payload = "";
|
||||
payload.reserve(200);
|
||||
json.printTo(payload);
|
||||
return payload;
|
||||
if (foundDevice(device)) {
|
||||
// expose the last known status of the bulb, for Hue integration
|
||||
attr_list.addAttribute(F(D_JSON_ZIGBEE_LIGHT)).setInt(getHueBulbtype(shortaddr)); // sign extend, 0xFF changed as -1
|
||||
// dump all known values
|
||||
attr_list.addAttribute(F("Reachable")).setBool(device.getReachable());
|
||||
if (device.validPower()) { attr_list.addAttribute(F("Power")).setUInt(device.getPower()); }
|
||||
if (device.validDimmer()) { attr_list.addAttribute(F("Dimmer")).setUInt(device.dimmer); }
|
||||
if (device.validColormode()) { attr_list.addAttribute(F("Colormode")).setUInt(device.colormode); }
|
||||
if (device.validCT()) { attr_list.addAttribute(F("CT")).setUInt(device.ct); }
|
||||
if (device.validSat()) { attr_list.addAttribute(F("Sat")).setUInt(device.sat); }
|
||||
if (device.validHue()) { attr_list.addAttribute(F("Hue")).setUInt(device.hue); }
|
||||
if (device.validX()) { attr_list.addAttribute(F("X")).setUInt(device.x); }
|
||||
if (device.validY()) { attr_list.addAttribute(F("Y")).setUInt(device.y); }
|
||||
}
|
||||
|
||||
Z_attribute_list attr_list_root;
|
||||
Z_attribute * attr_root;
|
||||
if (use_fname) {
|
||||
attr_root = &attr_list_root.addAttribute(fname);
|
||||
} else {
|
||||
attr_root = &attr_list_root.addAttribute(hex);
|
||||
}
|
||||
attr_root->setStrRaw(attr_list.toString(true).c_str());
|
||||
return attr_list_root.toString(true);
|
||||
}
|
||||
|
||||
// Dump the internal memory of Zigbee devices
|
||||
|
|
|
@ -124,6 +124,16 @@ uint16_t CxToCluster(uint8_t cx) {
|
|||
}
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
uint8_t ClusterToCx(uint16_t cluster) {
|
||||
for (uint8_t i=0; i<ARRAY_SIZE(Cx_cluster); i++) {
|
||||
if (pgm_read_word(&Cx_cluster[i]) == cluster) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// list of post-processing directives
|
||||
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
|
||||
{ Zuint8, Cx0000, 0x0000, Z_(ZCLVersion), 1 },
|
||||
|
@ -544,6 +554,25 @@ const __FlashStringHelper* zigbeeFindAttributeByName(const char *command,
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
//
|
||||
// Find attribute details: Name, Type, Multiplier by cuslter/attr_id
|
||||
//
|
||||
const __FlashStringHelper* zigbeeFindAttributeById(uint16_t cluster, uint16_t attr_id,
|
||||
uint8_t *attr_type, int8_t *multiplier) {
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint16_t conv_attr_id = pgm_read_word(&converter->attribute);
|
||||
|
||||
if ((conv_cluster == cluster) && (conv_attr_id == attr_id)) {
|
||||
if (multiplier) { *multiplier = pgm_read_byte(&converter->multiplier); }
|
||||
if (attr_type) { *attr_type = pgm_read_byte(&converter->type); }
|
||||
return (const __FlashStringHelper*) (Z_strings + pgm_read_word(&converter->name_offset));
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
class ZCLFrame {
|
||||
public:
|
||||
|
||||
|
@ -1051,6 +1080,11 @@ void ZCLFrame::generateSyntheticAttributes(Z_attribute_list& attr_list) {
|
|||
uint32_t ccccaaaa = (attr.key.id.cluster << 16) | attr.key.id.attr_id;
|
||||
|
||||
switch (ccccaaaa) { // 0xccccaaaa . c=cluster, a=attribute
|
||||
case 0x00010020: // BatteryVoltage
|
||||
if (attr_list.countAttribute(0x0001,0x0021) == 0) { // if it does not already contain BatteryPercentage
|
||||
uint32_t mv = attr.getUInt()*100;
|
||||
attr_list.addAttribute(0x0001, 0x0021).setUInt(toPercentageCR2032(mv) * 2);
|
||||
}
|
||||
case 0x0000FF01:
|
||||
syntheticAqaraSensor(attr_list, attr);
|
||||
break;
|
||||
|
@ -1095,35 +1129,30 @@ void ZCLFrame::generateCallBacks(Z_attribute_list& attr_list) {
|
|||
// Set timers to read back values.
|
||||
// If it's a device address, also set a timer for reachability test
|
||||
void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint = 0) {
|
||||
int32_t z_cat = -1;
|
||||
uint32_t wait_ms = 0;
|
||||
uint32_t wait_ms = 0xFFFF;
|
||||
|
||||
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) {
|
||||
if (0xFFFF != wait_ms) {
|
||||
if ((BAD_SHORTADDR != shortaddr) && (0 == endpoint)) {
|
||||
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);
|
||||
zigbee_devices.queueTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_READ_CLUSTER, 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);
|
||||
}
|
||||
|
@ -1170,16 +1199,27 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
|
|||
|
||||
// ZCL_CONFIGURE_REPORTING_RESPONSE
|
||||
void ZCLFrame::parseConfigAttributes(Z_attribute_list& attr_list) {
|
||||
uint32_t i = 0;
|
||||
uint32_t len = _payload.len();
|
||||
uint8_t status = _payload.get8(i);
|
||||
|
||||
Z_attribute_list attr_config_response;
|
||||
attr_config_response.addAttribute(F("Status")).setUInt(status);
|
||||
attr_config_response.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
|
||||
Z_attribute_list attr_config_list;
|
||||
for (uint32_t i=0; len >= i+4; i+=4) {
|
||||
uint8_t status = _payload.get8(i);
|
||||
uint16_t attr_id = _payload.get8(i+2);
|
||||
|
||||
Z_attribute_list attr_config_response;
|
||||
attr_config_response.addAttribute(F("Status")).setUInt(status);
|
||||
attr_config_response.addAttribute(F("StatusMsg")).setStr(getZigbeeStatusMessage(status).c_str());
|
||||
|
||||
const __FlashStringHelper* attr_name = zigbeeFindAttributeById(_cluster_id, attr_id, nullptr, nullptr);
|
||||
if (attr_name) {
|
||||
attr_config_list.addAttribute(attr_name).setStrRaw(attr_config_response.toString(true).c_str());
|
||||
} else {
|
||||
attr_config_list.addAttribute(_cluster_id, attr_id).setStrRaw(attr_config_response.toString(true).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Z_attribute &attr_1 = attr_list.addAttribute(F("ConfigResponse"));
|
||||
attr_1.setStrRaw(attr_config_response.toString(true).c_str());
|
||||
attr_1.setStrRaw(attr_config_list.toString(true).c_str());
|
||||
}
|
||||
|
||||
// ZCL_READ_REPORTING_CONFIGURATION_RESPONSE
|
||||
|
@ -1459,7 +1499,7 @@ void ZCLFrame::syntheticAqaraCubeOrButton(class Z_attribute_list &attr_list, cla
|
|||
// presentValue = x + 128 = 180º flip to side x on top
|
||||
// presentValue = x + 256 = push/slide cube while side x is on top
|
||||
// presentValue = x + 512 = double tap while side x is on top
|
||||
} else if (modelId.startsWith(F("lumi.remote"))) { // only for Aqara button
|
||||
} else if (modelId.startsWith(F("lumi.remote")) || modelId.startsWith(F("lumi.sensor_switch"))) { // only for Aqara buttons WXKG11LM & WXKG12LM
|
||||
int32_t val = attr.getInt();
|
||||
const __FlashStringHelper *aqara_click = F("click");
|
||||
const __FlashStringHelper *aqara_action = F("action");
|
||||
|
@ -1474,8 +1514,17 @@ void ZCLFrame::syntheticAqaraCubeOrButton(class Z_attribute_list &attr_list, cla
|
|||
case 2:
|
||||
attr_list.addAttribute(aqara_click).setStr(PSTR("double"));
|
||||
break;
|
||||
case 16:
|
||||
attr_list.addAttribute(aqara_action).setStr(PSTR("hold"));
|
||||
break;
|
||||
case 17:
|
||||
attr_list.addAttribute(aqara_action).setStr(PSTR("release"));
|
||||
break;
|
||||
case 18:
|
||||
attr_list.addAttribute(aqara_action).setStr(PSTR("shake"));
|
||||
break;
|
||||
case 255:
|
||||
attr_list.addAttribute(aqara_click).setStr(PSTR("release"));
|
||||
attr_list.addAttribute(aqara_action).setStr(PSTR("release"));
|
||||
break;
|
||||
default:
|
||||
attr_list.addAttribute(aqara_click).setUInt(val);
|
||||
|
@ -1534,11 +1583,10 @@ void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class
|
|||
}
|
||||
|
||||
/// Publish a message for `"Occupancy":0` when the timer expired
|
||||
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
void Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
Z_attribute_list attr_list;
|
||||
attr_list.addAttribute(F(OCCUPANCY)).setUInt(0);
|
||||
zigbee_devices.jsonPublishNow(shortaddr, attr_list);
|
||||
return 0; // Fix GCC 10.1 warning
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
|
@ -1616,4 +1664,78 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, Z_attribute_list& attr_
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Given an attribute string, retrieve all attribute details.
|
||||
// Input: the attribute has a key name, either: <cluster>/<attr> or <cluster>/<attr>%<type> or "<attribute_name>"
|
||||
// Ex: "0008/0000", "0008/0000%20", "Dimmer"
|
||||
// Use:
|
||||
// Z_attribute attr;
|
||||
// attr.setKeyName("0008/0000%20")
|
||||
// if (Z_parseAttributeKey(attr)) {
|
||||
// // ok
|
||||
// }
|
||||
//
|
||||
// Output:
|
||||
// The `attr` attribute has the correct cluster, attr_id, attr_type, attr_multiplier
|
||||
// Note: the attribute value is unchanged and unparsed
|
||||
//
|
||||
// Note: if the type is specified in the key, the multiplier is not applied, no conversion happens
|
||||
bool Z_parseAttributeKey(class Z_attribute & attr) {
|
||||
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
|
||||
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
|
||||
if (attr.key_is_str) {
|
||||
const char * key = attr.key.key;
|
||||
char * delimiter = strchr(key, '/');
|
||||
char * delimiter2 = strchr(key, '%');
|
||||
if (delimiter) {
|
||||
uint16_t attr_id = 0xFFFF;
|
||||
uint16_t cluster_id = 0xFFFF;
|
||||
uint8_t type_id = Zunk;
|
||||
|
||||
cluster_id = strtoul(key, &delimiter, 16);
|
||||
if (!delimiter2) {
|
||||
attr_id = strtoul(delimiter+1, nullptr, 16);
|
||||
} else {
|
||||
attr_id = strtoul(delimiter+1, &delimiter2, 16);
|
||||
type_id = strtoul(delimiter2+1, nullptr, 16);
|
||||
}
|
||||
attr.setKeyId(cluster_id, attr_id);
|
||||
attr.attr_type = type_id;
|
||||
}
|
||||
}
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id);
|
||||
|
||||
// do we already know the type, i.e. attribute and cluster are also known
|
||||
if (Zunk == attr.attr_type) {
|
||||
// scan attributes to find by name, and retrieve type
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
bool match = false;
|
||||
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
|
||||
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint8_t local_type_id = pgm_read_byte(&converter->type);
|
||||
int8_t local_multiplier = pgm_read_byte(&converter->multiplier);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
|
||||
|
||||
if (!attr.key_is_str) {
|
||||
if ((attr.key.id.cluster == local_cluster_id) && (attr.key.id.attr_id == local_attr_id)) {
|
||||
attr.attr_type = local_type_id;
|
||||
break;
|
||||
}
|
||||
} else if (pgm_read_word(&converter->name_offset)) {
|
||||
const char * key = attr.key.key;
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
|
||||
if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) {
|
||||
// match
|
||||
attr.setKeyId(local_cluster_id, local_attr_id);
|
||||
attr.attr_type = local_type_id;
|
||||
attr.attr_multiplier = local_multiplier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (Zunk != attr.attr_type) ? true : false;
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -188,16 +188,14 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
|
|||
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
|
||||
}
|
||||
|
||||
// returns true if char is 'x', 'y' or 'z'
|
||||
|
@ -349,6 +347,10 @@ void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster,
|
|||
if ((0 != xyz.z) && (0xFF != xyz.z)) {
|
||||
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
|
||||
}
|
||||
// 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:
|
||||
|
|
|
@ -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,9 +1089,31 @@ 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);
|
||||
|
@ -983,6 +1134,170 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) {
|
|||
}));
|
||||
}
|
||||
|
||||
//
|
||||
// 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
|
||||
|
@ -1189,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:
|
||||
|
@ -1279,9 +1596,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
|
|||
\*********************************************************************************************/
|
||||
|
||||
// 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) {
|
||||
void Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||
zigbee_devices.jsonPublishFlush(shortaddr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
@ -1363,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
|
||||
|
@ -1413,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
|
||||
|
@ -1451,20 +1768,21 @@ 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 uint16_t *attr_list, size_t attr_len) {
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
JsonObject& json_out = jsonBuffer.createObject();
|
||||
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list_ids, size_t attr_len) {
|
||||
Z_attribute_list attr_list;
|
||||
|
||||
for (uint32_t i=0; i<attr_len; i++) {
|
||||
uint16_t attr = attr_list[i];
|
||||
uint32_t ccccaaaa = (cluster << 16) || attr;
|
||||
uint16_t attr_id = attr_list_ids[i];
|
||||
uint32_t ccccaaaa = (cluster << 16) | attr_id;
|
||||
Z_attribute attr;
|
||||
attr.setKeyId(cluster, attr_id);
|
||||
|
||||
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
|
||||
case 0x00000004: attr.setStr(PSTR(USE_ZIGBEE_MANUFACTURER)); break; // Manufacturer
|
||||
case 0x00000005: attr.setStr(PSTR(USE_ZIGBEE_MODELID)); break; // ModelId
|
||||
#ifdef USE_LIGHT
|
||||
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
|
||||
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
|
||||
case 0x00060000: attr.setUInt(Light.power ? 1 : 0); break; // Power
|
||||
case 0x00080000: attr.setUInt(LightGetDimmer(0)); break; // Dimmer
|
||||
|
||||
case 0x03000000: // Hue
|
||||
case 0x03000001: // Sat
|
||||
|
@ -1482,50 +1800,70 @@ 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 (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(); }
|
||||
if (0x0000 == attr_id) { attr.setUInt(changeUIntScale(hue, 0, 360, 0, 254)); }
|
||||
if (0x0001 == attr_id) { attr.setUInt(changeUIntScale(sat, 0, 255, 0, 254)); }
|
||||
if (0x0003 == attr_id) { attr.setUInt(uxy[0]); }
|
||||
if (0x0004 == attr_id) { attr.setUInt(uxy[1]); }
|
||||
if (0x0007 == attr_id) { attr.setUInt(LightGetColorTemp()); }
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case 0x000A0000: // Time
|
||||
json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time;
|
||||
attr.setUInt((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;
|
||||
attr.setUInt(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
|
||||
attr.setUInt((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;
|
||||
attr.setUInt(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);
|
||||
attr.setUInt(Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time));
|
||||
break;
|
||||
}
|
||||
if (!attr.isNone()) {
|
||||
Z_parseAttributeKey(attr);
|
||||
attr_list.addAttribute(cluster, attr_id) = attr;
|
||||
}
|
||||
}
|
||||
|
||||
if (json_out.size() > 0) {
|
||||
SBuffer buf(200);
|
||||
for (const auto & attr : attr_list) {
|
||||
if (!ZbAppendWriteBuf(buf, attr, true)) { // true = need status indicator in Read Attribute Responses
|
||||
return; // error
|
||||
}
|
||||
}
|
||||
|
||||
if (buf.len() > 0) {
|
||||
// we have a non-empty output
|
||||
|
||||
// log first
|
||||
String msg("");
|
||||
msg.reserve(100);
|
||||
json_out.printTo(msg);
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Auto-responder: ZbSend {\"Device\":\"0x%04X\""
|
||||
",\"Cluster\":\"0x%04X\""
|
||||
",\"Endpoint\":%d"
|
||||
",\"Response\":%s}"
|
||||
),
|
||||
srcaddr, cluster, endpoint,
|
||||
msg.c_str());
|
||||
attr_list.toString().c_str());
|
||||
|
||||
// send
|
||||
const JsonVariant &json_out_v = json_out;
|
||||
ZbSendReportWrite(json_out_v, srcaddr, 0 /* group */,cluster, endpoint, 0 /* manuf */, ZCL_READ_ATTRIBUTES_RESPONSE);
|
||||
// all good, send the packet
|
||||
uint8_t seq = zigbee_devices.getNextSeqNumber(srcaddr);
|
||||
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
|
||||
srcaddr,
|
||||
0x0000,
|
||||
cluster /*cluster*/,
|
||||
endpoint,
|
||||
ZCL_READ_ATTRIBUTES_RESPONSE,
|
||||
0x0000, /* manuf */
|
||||
false /* not cluster specific */,
|
||||
false /* no response */,
|
||||
seq, /* zcl transaction id */
|
||||
buf.getBuffer(), buf.len()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -595,7 +595,9 @@ int32_t ZigbeeProcessInputEZSP(class SBuffer &buf) {
|
|||
case EZSP_getNetworkParameters: // 2800
|
||||
case EZSP_sendUnicast: // 3400
|
||||
case EZSP_sendBroadcast: // 3600
|
||||
case EZSP_sendMulticast: // 3800
|
||||
case EZSP_messageSentHandler: // 3F00
|
||||
case EZSP_incomingMessageHandler: // 4500
|
||||
case EZSP_setConfigurationValue: // 5300
|
||||
case EZSP_setPolicy: // 5500
|
||||
case 0x0059: // 5900 - supposedly removed by still happening
|
||||
|
|
|
@ -212,6 +212,34 @@ void ZbApplyMultiplier(double &val_d, int8_t multiplier) {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Send Attribute Write, apply mutlipliers before
|
||||
//
|
||||
bool ZbAppendWriteBuf(SBuffer & buf, const Z_attribute & attr, bool prepend_status_ok) {
|
||||
double val_d = attr.getFloat();
|
||||
const char * val_str = attr.getStr();
|
||||
|
||||
if (attr.key_is_str) { return false; }
|
||||
if (attr.isNum() && (1 != attr.attr_multiplier)) {
|
||||
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||
}
|
||||
|
||||
// push the value in the buffer
|
||||
buf.add16(attr.key.id.attr_id); // prepend with attribute identifier
|
||||
if (prepend_status_ok) {
|
||||
buf.add8(Z_SUCCESS); // status OK = 0x00
|
||||
}
|
||||
buf.add8(attr.attr_type); // prepend with attribute type
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type);
|
||||
if (res < 0) {
|
||||
// remove the attribute type we just added
|
||||
// buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3));
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Unsupported attribute type %04X/%04X '0x%02X'"), attr.key.id.cluster, attr.key.id.attr_id, attr.attr_type);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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, uint8_t operation) {
|
||||
|
@ -226,99 +254,42 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
const char *key = it->key;
|
||||
const JsonVariant &value = it->value;
|
||||
|
||||
uint16_t attr_id = 0xFFFF;
|
||||
uint16_t cluster_id = 0xFFFF;
|
||||
uint8_t type_id = Znodata;
|
||||
int8_t multiplier = 1; // multiplier to adjust the key value
|
||||
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
|
||||
const char* val_str = ""; // variant as string
|
||||
|
||||
// check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id
|
||||
// alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes)
|
||||
char * delimiter = strchr(key, '/');
|
||||
char * delimiter2 = strchr(key, '%');
|
||||
if (delimiter) {
|
||||
cluster_id = strtoul(key, &delimiter, 16);
|
||||
if (!delimiter2) {
|
||||
attr_id = strtoul(delimiter+1, nullptr, 16);
|
||||
} else {
|
||||
attr_id = strtoul(delimiter+1, &delimiter2, 16);
|
||||
type_id = strtoul(delimiter2+1, nullptr, 16);
|
||||
}
|
||||
}
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id);
|
||||
|
||||
// do we already know the type, i.e. attribute and cluster are also known
|
||||
if (Znodata == type_id) {
|
||||
// scan attributes to find by name, and retrieve type
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) {
|
||||
const Z_AttributeConverter *converter = &Z_PostProcess[i];
|
||||
bool match = false;
|
||||
uint16_t local_attr_id = pgm_read_word(&converter->attribute);
|
||||
uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short));
|
||||
uint8_t local_type_id = pgm_read_byte(&converter->type);
|
||||
int8_t local_multiplier = pgm_read_byte(&converter->multiplier);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id);
|
||||
|
||||
if (delimiter) {
|
||||
if ((cluster_id == local_cluster_id) && (attr_id == local_attr_id)) {
|
||||
type_id = local_type_id;
|
||||
break;
|
||||
}
|
||||
} else if (pgm_read_word(&converter->name_offset)) {
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name);
|
||||
if (0 == strcasecmp_P(key, Z_strings + pgm_read_word(&converter->name_offset))) {
|
||||
// match
|
||||
cluster_id = local_cluster_id;
|
||||
attr_id = local_attr_id;
|
||||
type_id = local_type_id;
|
||||
multiplier = local_multiplier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buffer ready, do some sanity checks
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X, type_id = 0x%02X"), cluster_id, attr_id, type_id);
|
||||
if ((0xFFFF == attr_id) || (0xFFFF == cluster_id)) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key);
|
||||
return;
|
||||
}
|
||||
if (Znodata == type_id) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (0xFFFF == cluster) {
|
||||
cluster = cluster_id; // set the cluster for this packet
|
||||
} else if (cluster != cluster_id) {
|
||||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ////////////////////////////////////////////////////////////////////////////////
|
||||
// Split encoding depending on message
|
||||
if (operation != ZCL_CONFIGURE_REPORTING) {
|
||||
// apply multiplier if needed
|
||||
val_d = value.as<double>();
|
||||
val_str = value.as<const char*>();
|
||||
ZbApplyMultiplier(val_d, multiplier);
|
||||
|
||||
// push the value in the buffer
|
||||
buf.add16(attr_id); // prepend with attribute identifier
|
||||
if (operation == ZCL_READ_ATTRIBUTES_RESPONSE) {
|
||||
buf.add8(Z_SUCCESS); // status OK = 0x00
|
||||
}
|
||||
buf.add8(type_id); // prepend with attribute type
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id);
|
||||
if (res < 0) {
|
||||
// remove the attribute type we just added
|
||||
// buf.setLen(buf.len() - (operation == ZCL_READ_ATTRIBUTES_RESPONSE ? 4 : 3));
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
|
||||
Z_attribute attr;
|
||||
attr.setKeyName(key);
|
||||
if (Z_parseAttributeKey(attr)) {
|
||||
// Buffer ready, do some sanity checks
|
||||
if (0xFFFF == cluster) {
|
||||
cluster = attr.key.id.cluster; // set the cluster for this packet
|
||||
} else if (cluster != attr.key.id.cluster) {
|
||||
ResponseCmndChar_P(PSTR("No more than one cluster id per command"));
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (attr.key_is_str) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key);
|
||||
return;
|
||||
}
|
||||
if (Zunk == attr.attr_type) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.is<const char*>()) {
|
||||
attr.setStr(value.as<const char*>());
|
||||
} else if (value.is<double>()) {
|
||||
attr.setFloat(value.as<float>());
|
||||
}
|
||||
|
||||
double val_d = 0; // I try to avoid `double` but this type capture both float and (u)int32_t without prevision loss
|
||||
const char* val_str = ""; // variant as string
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Split encoding depending on message
|
||||
if (operation != ZCL_CONFIGURE_REPORTING) {
|
||||
if (!ZbAppendWriteBuf(buf, attr, operation == ZCL_READ_ATTRIBUTES_RESPONSE)) {
|
||||
return; // error
|
||||
}
|
||||
} else {
|
||||
// ////////////////////////////////////////////////////////////////////////////////
|
||||
// ZCL_CONFIGURE_REPORTING
|
||||
|
@ -350,7 +321,7 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
if (nullptr != &val_attr_rc) {
|
||||
val_d = val_attr_rc.as<double>();
|
||||
val_str = val_attr_rc.as<const char*>();
|
||||
ZbApplyMultiplier(val_d, multiplier);
|
||||
ZbApplyMultiplier(val_d, attr.attr_multiplier);
|
||||
}
|
||||
|
||||
// read TimeoutPeriod
|
||||
|
@ -358,22 +329,22 @@ void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t
|
|||
const JsonVariant &val_attr_timeout = GetCaseInsensitive(attr_config, PSTR("TimeoutPeriod"));
|
||||
if (nullptr != &val_attr_timeout) { attr_timeout = strToUInt(val_attr_timeout); }
|
||||
|
||||
bool attr_discrete = Z_isDiscreteDataType(type_id);
|
||||
bool attr_discrete = Z_isDiscreteDataType(attr.attr_type);
|
||||
|
||||
// all fields are gathered, output the butes into the buffer, ZCL 2.5.7.1
|
||||
// common bytes
|
||||
buf.add8(attr_direction ? 0x01 : 0x00);
|
||||
buf.add16(attr_id);
|
||||
buf.add16(attr.key.id.attr_id);
|
||||
if (attr_direction) {
|
||||
buf.add16(attr_timeout);
|
||||
} else {
|
||||
buf.add8(type_id);
|
||||
buf.add8(attr.attr_type);
|
||||
buf.add16(attr_min_interval);
|
||||
buf.add16(attr_max_interval);
|
||||
if (!attr_discrete) {
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, type_id);
|
||||
int32_t res = encodeSingleAttribute(buf, val_d, val_str, attr.attr_type);
|
||||
if (res < 0) {
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id);
|
||||
Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, attr.attr_type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -556,6 +527,7 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
|
||||
uint16_t val = strToUInt(val_attr);
|
||||
if (val_attr.is<JsonArray>()) {
|
||||
// value is an array []
|
||||
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
|
||||
attrs_len = attr_arr.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
|
@ -569,6 +541,7 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
i += attr_item_len - 2 - attr_item_offset; // normally 0
|
||||
}
|
||||
} else if (val_attr.is<JsonObject>()) {
|
||||
// value is an object {}
|
||||
const JsonObject& attr_obj = val_attr.as<const JsonObject&>();
|
||||
attrs_len = attr_obj.size() * attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
|
@ -619,10 +592,13 @@ void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr
|
|||
|
||||
attrs_len = actual_attr_len;
|
||||
} else {
|
||||
attrs_len = attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
attrs[0 + attr_item_offset] = val & 0xFF; // little endian
|
||||
attrs[1 + attr_item_offset] = val >> 8;
|
||||
// value is a literal
|
||||
if (0xFFFF != cluster) {
|
||||
attrs_len = attr_item_len;
|
||||
attrs = (uint8_t*) calloc(attrs_len, 1);
|
||||
attrs[0 + attr_item_offset] = val & 0xFF; // little endian
|
||||
attrs[1 + attr_item_offset] = val >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs_len > 0) {
|
||||
|
@ -1307,6 +1283,13 @@ void CmndZbConfig(void) {
|
|||
const JsonVariant &val_txradio = GetCaseInsensitive(json, PSTR("TxRadio"));
|
||||
if (nullptr != &val_txradio) { zb_txradio_dbm = strToUInt(val_txradio); }
|
||||
|
||||
// if network key is zero, we generate a truly random key with a hardware generator from ESP
|
||||
if ((0 == zb_precfgkey_l) && (0 == zb_precfgkey_h)) {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "generating random Zigbee network key"));
|
||||
zb_precfgkey_l = (uint64_t)HwRandom() << 32 | HwRandom();
|
||||
zb_precfgkey_h = (uint64_t)HwRandom() << 32 | HwRandom();
|
||||
}
|
||||
|
||||
// Check if a parameter was changed after all
|
||||
if ( (zb_channel != Settings.zb_channel) ||
|
||||
(zb_pan_id != Settings.zb_pan_id) ||
|
||||
|
|
|
@ -90,7 +90,7 @@ void BuzzerBeep(uint32_t count, uint32_t on, uint32_t off, uint32_t tune, uint32
|
|||
Buzzer.count = count * 2; // Start buzzer
|
||||
|
||||
// We can use PWM mode for buzzer output if enabled.
|
||||
if (Settings.flag4.buzzer_freq_mode) { // SetOption110 - Enable frequency output mode for buzzer
|
||||
if (Settings.flag4.buzzer_freq_mode) { // SetOption111 - Enable frequency output mode for buzzer
|
||||
Buzzer.freq_mode = 1;
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -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);
|
||||
mp3 = new AudioGeneratorMP3();
|
||||
|
||||
if (mp3ram) {
|
||||
mp3 = new AudioGeneratorMP3(mp3ram, preallocateCodecSize);
|
||||
} else {
|
||||
mp3 = new AudioGeneratorMP3();
|
||||
}
|
||||
mp3->begin(id3, out);
|
||||
|
||||
if (I2S_Task) {
|
||||
|
|
|
@ -0,0 +1,626 @@
|
|||
/*
|
||||
xdrv_43_mlx90640.ino - MLX90640 support for Tasmota
|
||||
|
||||
Copyright (C) 2020 Christian Baars and Theo Arends
|
||||
|
||||
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/>.
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------------------
|
||||
Version yyyymmdd Action Description
|
||||
--------------------------------------------------------------------------------------------
|
||||
0.9.0.0 20200827 started - based on https://github.com/melexis/mlx90640-library
|
||||
*/
|
||||
|
||||
#ifdef USE_I2C
|
||||
#ifdef USE_MLX90640
|
||||
|
||||
#define MLX90640_ADDRESS 0x33
|
||||
#define MLX90640_POI_NUM 6 //some parts of the JS are hardcoded for 6!!
|
||||
|
||||
/*********************************************************************************************\
|
||||
* MLX90640
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XDRV_43 43
|
||||
#define XI2C_53 53 // See I2CDEVICES.md
|
||||
#include <MLX90640_API.h>
|
||||
|
||||
const char MLX90640type[] PROGMEM = "MLX90640";
|
||||
|
||||
#ifdef USE_WEBSERVER
|
||||
#define WEB_HANDLE_MLX90640 "mlx"
|
||||
const char HTTP_BTN_MENU_MLX90640[] PROGMEM = "<p><form action='" WEB_HANDLE_MLX90640 "' method='get'><button>MLX90640</button></form></p>";
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
struct {
|
||||
uint32_t type:1;
|
||||
uint32_t ready:1;
|
||||
uint32_t dumpedEE:1;
|
||||
uint32_t extractedParams:1;
|
||||
paramsMLX90640 *params;
|
||||
float Ta;
|
||||
uint16_t Frame[834];
|
||||
float To[768];
|
||||
uint8_t pois[2*MLX90640_POI_NUM] = {2,1, 30,1, 10,12, 22,12, 2,23, 30,23}; // {x1,y1,x2,y2,...,x6,y6}
|
||||
} MLX90640;
|
||||
|
||||
/*********************************************************************************************\
|
||||
* commands
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define D_CMND_MLX90640 "MLX"
|
||||
|
||||
const char S_JSON_MLX90640_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MLX90640 "%s\":%d}";
|
||||
const char S_JSON_MLX90640_COMMAND[] PROGMEM = "{\"" D_CMND_MLX90640 "%s\"}";
|
||||
const char kMLX90640_Commands[] PROGMEM = "POI";
|
||||
|
||||
enum MLX90640_Commands { // commands useable in console or rules
|
||||
CMND_MLX90640_POI // MLXPOIn xxyy - set POI number n to x,y
|
||||
};
|
||||
|
||||
/************************************************************************\
|
||||
* Web GUI
|
||||
\************************************************************************/
|
||||
#ifdef USE_WEBSERVER
|
||||
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_1_SNS_SIZE = 389;
|
||||
const char HTTP_MLX90640_1_SNS_COMPRESSED[] PROGMEM = "\x3D\x3C\x1F\xF4\x65\x2A\x2B\x32\x18\xCF\x87\xDD\x33\x65\x1D\x86\xBB\x33\xB0\x41"
|
||||
"\xA4\x7D\x9F\x81\xE7\x7A\x90\xDB\x18\x7C\x3B\xA6\x76\x10\xB6\x75\x1B\x0E\x43\xA8"
|
||||
"\x8C\x8E\x43\xA8\x8D\x87\x28\xEA\x23\x23\x94\x77\x8F\x87\xE1\x02\x0D\x13\xAC\xD8"
|
||||
"\x72\x1D\xE3\xD6\x77\x48\xC8\xE5\x1D\x64\x6C\x39\x47\x78\xEC\x3B\xA4\x64\x72\x1D"
|
||||
"\x64\x6C\x39\x0E\xF1\xDB\x23\x61\xCA\x3C\x10\x20\xE3\x3A\x36\xC7\x9A\x3E\x2E\x63"
|
||||
"\xE8\xB4\x6D\x8F\x33\xC1\x9D\xFD\x07\x7C\x67\x7E\x3A\x83\xA3\x61\xD4\x3D\xF1\x0F"
|
||||
"\x06\x77\xF4\x3C\x43\x0D\x87\x50\xCC\xD3\xE1\xEF\x1E\xF9\xE0\xCE\xFE\xBE\x56\x7C"
|
||||
"\x3D\xE3\xDF\x3C\x18\x17\xC1\xD6\xE7\x21\xE7\x44\x37\x05\xF9\x90\xCC\xF1\xDD\x04"
|
||||
"\x2C\x65\x33\x3A\x3B\xC8\xF6\x82\x0E\x87\xF6\x1D\x23\xE0\x21\x66\x87\x41\xE7\x44"
|
||||
"\x3B\x05\xF0\x9B\xC3\xC4\x18\x5A\xFA\x8B\xEC\x3A\x3B\xA7\x78\xF0\x67\x7F\x46\xC4"
|
||||
"\x7C\x4C\xCE\x8E\x81\x85\xAF\xA8\x8D\x87\x5F\xD8\x74\x74\x09\x98\xA3\xC6\x98\x3B"
|
||||
"\xA6\xC3\xF0\xE5\xD3\x3B\xC7\xB4\x8D\x87\xC3\x97\x11\xE0\xF7\x17\xDD\x0B\xFF\x23"
|
||||
"\xDA\x6C\x3C\xD1\x0D\xBA\x14\x74\x30\x16\x67\xCE\xE8\xDB\x18\x77\x4D\x87\x51\xC6"
|
||||
"\x75\x5D\x33\xA9\x9D\x57\x0E\x88\xEF\x1D\xE3\xA8\x8C\x81\x32\xF9\xDD\x04\x5D\x04"
|
||||
"\x8C\x91\xD6\xBE\xC3\xA3\xA5\x60\xC3\xBC\x75\x1C\x67\x55\x63\x3A\x99\xD5\x56\x74"
|
||||
"\x47\x78\xEF\x1E\xE3\xC1\xEE";
|
||||
#define HTTP_MLX90640_1_SNS Decompress(HTTP_MLX90640_1_SNS_COMPRESSED,HTTP_MLX90640_1_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_1_SNS[] PROGMEM =
|
||||
"<script type='text/javascript'>"
|
||||
"const map=(value,x1,y1,x2,y2)=>(value-x1)*(y2-x2)/(y1-x1)+x2;"
|
||||
"const image = new Image;"
|
||||
"var canvas,ctx,grd;"
|
||||
"var gPx,poi=[];var rA=[];" //gradient pixel, POI's, rawArray
|
||||
"function getMousePos(canvas, evt) {"
|
||||
"const rect = canvas.getBoundingClientRect();"
|
||||
"var x = evt.clientX-rect.left;"
|
||||
"if(x>320){"
|
||||
"x=319;"
|
||||
"}"
|
||||
"return {"
|
||||
"x: Math.floor(map(x,0,320,0,31.9)),"
|
||||
"y: Math.floor(map((evt.clientY - rect.top),0,240,0,23.9))};"
|
||||
"}"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_2a_SNS_SIZE = 632;
|
||||
const char HTTP_MLX90640_2a_SNS_COMPRESSED[] PROGMEM = "\x33\xBF\xA0\xB7\x9A\x3E\x23\x8C\xF0\x5E\x74\x5B\xD4\xFE\x67\x61\x1D\xD3\x02\xF8"
|
||||
"\x3A\xDC\xE3\xBA\x77\x91\xED\xF8\x47\x74\xFB\x16\x11\xF6\x75\x05\xBC\xCE\xF1\xE0"
|
||||
"\xF7\x1D\x47\x29\xB3\xBC\x78\x20\x43\xBA\xBE\x11\xDD\xF1\xD4\x66\x77\x8F\x69\x9D"
|
||||
"\xFD\x1B\x3E\x7C\xE6\x3E\x88\xD8\x43\x48\x22\x15\x54\x30\xBE\xCD\x42\xDF\xA8\xEE"
|
||||
"\x9D\xE3\xC1\xB3\xE7\x4C\xEF\xBB\x10\xCB\xD5\x74\xC3\x15\x7C\x3C\xCF\x80\x8B\xA0"
|
||||
"\x1E\xDD\x30\x77\x4D\x9F\x3A\x7D\xD8\x86\x45\xEA\xBA\x67\xC3\xE1\xCC\x3F\x47\xE8"
|
||||
"\x8D\x9F\x3A\x7A\xAE\x85\xF8\xF8\x7C\x39\x4D\x90\x20\xE7\xD6\x43\x91\xF1\x1B\x3E"
|
||||
"\x74\xFB\xF0\xCC\xEF\x33\xC1\x9D\xFD\x69\xE3\x4C\x23\xBB\x64\x38\xE8\x38\xCA\x99"
|
||||
"\x04\xF8\x7A\x85\x1F\x0F\x87\x2B\x99\xDE\x3B\x87\xB4\x8C\xEF\xE8\xC1\x5A\x3E\x2E"
|
||||
"\x63\xE8\x8C\x05\x97\x47\x2E\x88\xAF\xFF\xB3\x23\xBB\x64\x38\xEF\x1E\x02\xDE\x67"
|
||||
"\xC3\x05\x67\xBC\x71\x9E\xF9\xE0\xB4\xC1\xDD\x0B\x79\x9F\x87\x23\x6C\xEF\x1E\xD2"
|
||||
"\x0B\x79\x9F\x02\xDE\x67\x59\xC8\xDB\x3C\x13\x1C\x77\x4F\xBB\x39\x0F\xB3\xBC\x74"
|
||||
"\x2D\xEE\x7F\x21\x45\x44\x34\x82\x3E\x05\xBC\xCE\x95\x84\x63\x4D\x8C\x43\xBA\x72"
|
||||
"\x88\x10\xB2\x73\x8C\xF7\x11\x8C\xFA\x3B\xBA\x7C\x39\x0F\x07\x70\xB5\x1E\x88\xC1"
|
||||
"\x59\xD0\x27\xC3\xD4\x28\xF0\xB4\xED\x9D\xB3\xBC\x8F\x6A\xF9\x59\xEF\x69\xDB\x3B"
|
||||
"\xA1\x6F\x33\xD6\x73\xB0\xEF\x1D\x70\xF7\xCE\xE1\xF0\xEE\x11\x82\xB3\xDE\xD3\xDF"
|
||||
"\x3C\x1E\xE0\x42\xE3\x90\x2D\xE6\x76\xCE\x42\x04\x5D\xB0\xE4\x3B\xC7\x70\x81\x07"
|
||||
"\x6B\x38\xCF\x04\x37\x3C\x77\x4E\xF1\xE0\xF7\x1E\xE0\x4E\xE1\x28\xE4\xA2\x04\x1E"
|
||||
"\x1F\x0C\x8F\x9C\xC7\xD1\x0B\xDE\xA3\x9F\x20\x45\xE1\x0C\x10\xB7\x13\x8C\x81\x07"
|
||||
"\x71\x32\x04\x8D\xC0\xF6\x8C\xCD\x3D\xED\x3B\x0E\x51\xEF\x9F\x0F\x78\x8C\x8F\x7B"
|
||||
"\x4F\x7C\xEA\x46\x47\xBD\xA8\xED\xA3\x90\xF7\xCF\x7C\xF0\x77\x0E\xD9\xDB\x2D\x3C"
|
||||
"\x1E\xE3\xDC\x7B\x8F\x07\xB8\xF0\x08\xDC\x67\x15\x19\x0C\x68\xF8\x8F\xBB\xFF\xEC"
|
||||
"\xC8\x70\xB3\x06\x1F\xCF\xB3\xC1\xB3\xE7\x4C\x18\xF8\xEE\x9F\x64\x3C\x4C\xA8\xFB"
|
||||
"\x3A\x8F\xB3\xB0\x68\x46\xC3\xB4\x7D\x9D\xBF\x1D\xB3\xEC\xF8\x7D\x9D\xB3\x33\xAA"
|
||||
"\xBE\x2D\x9D\xE3\xC1\xB3\xE7\x4F\x3E\x10\xEE\x9D\xE3\xC1\xEE\x3C\x1B";
|
||||
#define HTTP_MLX90640_2a_SNS Decompress(HTTP_MLX90640_2a_SNS_COMPRESSED,HTTP_MLX90640_2a_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_2a_SNS[] PROGMEM =
|
||||
"var line = 0;"
|
||||
"setInterval(function() {"
|
||||
"rl('ul',line);" // 0 = do NOT force refresh
|
||||
"},200);"
|
||||
"function rl(s,v){" //source, value
|
||||
"var xr=new XMLHttpRequest();"
|
||||
"xr.onreadystatechange=function(){"
|
||||
"if(xr.readyState==4&&xr.status==200){"
|
||||
"var aB = xr.response;" // arrayBuffer
|
||||
"var i;"
|
||||
"if (aB.byteLength==260) {" // 2 lines of pixel data
|
||||
"var fA = new Float32Array(aB);" //floatArray
|
||||
"line=fA[0];"
|
||||
"if(line>1000){line=line-1000;eb('a1').innerHTML=line.toFixed(2);line=0}" //ambient hack
|
||||
"for (i=1; i < fA.length; i++) { "
|
||||
"rA[i+(line*64)-1] = fA[i];"
|
||||
"}"
|
||||
"line = line+1;"
|
||||
"if(line>11) {line=0;mos();"
|
||||
//"console.log(rA);"
|
||||
"}"
|
||||
"}"
|
||||
"if (aB.byteLength==12)"
|
||||
"{var y=new Uint8Array(aB);"
|
||||
"for (i=0; i < y.length; i++){"
|
||||
"poi[i/2]=[y[i], y[i + 1]]; ++i;"
|
||||
"}"
|
||||
"}"
|
||||
"};"
|
||||
"};"
|
||||
"xr.responseType = 'arraybuffer';"
|
||||
"xr.open('GET','/mlx?'+s+'='+v,true);"
|
||||
"xr.send();"
|
||||
"};"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_2b_SNS_SIZE = 389;
|
||||
const char HTTP_MLX90640_2b_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x43\x73\xC7\x74\xEF\x1E\xD3\x3B\xFA\xCF\x06\x8F\x88\x4C\x0C"
|
||||
"\x58\xD7\xD4\x74\x0F\xEE\xE9\x93\x09\x8D\x7D\x47\x74\xFB\x0E\xF8\xCE\xFC\x7D\x9D"
|
||||
"\xE3\xC6\x78\x33\xA0\xFE\x89\x42\x91\xF1\x1C\xBA\x3C\x16\x78\x33\xA0\xA7\xA3\xC2"
|
||||
"\xA9\x1F\x11\xCA\xC3\xC1\x19\xDF\xD6\x07\x46\xC4\x7C\x59\xE0\xCE\x83\xCE\x88\x3C"
|
||||
"\xEA\x66\xCA\x3B\xA7\xD9\xCA\x21\x0F\xB3\xBC\x78\x31\x9F\x47\x74\xCE\xFE\xB4\xF8"
|
||||
"\x71\x9E\x0B\x4F\x43\x95\x87\x82\xD3\xB6\x76\xCE\xF1\xED\x04\x4A\x66\xB3\xE1\xC6"
|
||||
"\x78\x23\x59\xE8\x72\xE8\xF0\x46\xB3\xB6\x76\xC1\x07\x74\x32\x47\xC4\x72\xAD\x1D"
|
||||
"\xC3\xAC\xEE\x11\x0D\xBA\x14\x74\x30\x16\x67\xCE\xE8\xDB\x18\x77\x7E\x56\x7B\xC7"
|
||||
"\x77\x4F\x59\xCB\xA3\xBC\x76\xC8\xD6\x7B\xE7\x51\xC6\x75\x63\x3A\x99\xD5\x56\x8E"
|
||||
"\xF1\xDE\x3C\x16\x07\x46\xC3\xA1\x8D\x08\x22\xF5\x19\x04\xD1\xF1\x1F\x7F\x1E\x1C"
|
||||
"\x77\x4F\xB4\x76\xD0\xF1\x0C\x36\x1D\x04\xBA\xB3\xDE\x3B\xA6\x47\xAC\xE6\x1D\xE3"
|
||||
"\xDF\x3B\x87\x6C\xEE\x1F\x67\x51\x02\x6D\x43\xB6\x72\x1E\xF9\xDC\x3B\x64\x0A\x15"
|
||||
"\x4E\x51\xEF\x9D\xC3\xB6\x77\x0F\xB3\xBC\x7D\x90\x22\xE7\xE5\xF6\x1D\x1D\xD3\x59"
|
||||
"\xEB\x39\x0E\xA2\xD3\xD6\x72\x1D\x50\xEA\x87\x78\xF0\x7B\x8F\x71\x68\xDB\x1E\x67"
|
||||
"\x4F\x7C\x34\x7C\xCF\x06\x74\xAC\x21\x2E\xAC\x85\x97\xC8\x23\xBA\x7D\xE8\xDB\x1E"
|
||||
"\x67\x60\xCE\x1E\x3E\xCE\xF1\xE0\xF7\x1B";
|
||||
#define HTTP_MLX90640_2b_SNS Decompress(HTTP_MLX90640_2b_SNS_COMPRESSED,HTTP_MLX90640_2b_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_2b_SNS[] PROGMEM =
|
||||
"function mos(){" //map off screen"
|
||||
"var osc = document.createElement('canvas');" //offscreen
|
||||
"osc.width = 32;"
|
||||
"osc.height = 24;"
|
||||
"var octx = osc.getContext('2d');"
|
||||
"for (var i=0;i<24;i++){"
|
||||
"for (var j=0;j<32;j++){"
|
||||
"var y = 239 - Math.floor(map(rA[(i*32)+j],0,40,0,239));" // 40 is max. temp for heat map
|
||||
// console.log(gPx.data[y],gPx.data[y+1],gPx.data[y+2]);
|
||||
"octx.fillStyle = 'rgb(' + gPx.data[(y*4)] + ',' + gPx.data[(y*4)+1] +',' + gPx.data[(y*4)+2] + ')';"
|
||||
"octx.fillRect(j*1,i*1,1,1);"
|
||||
"}"
|
||||
"}"
|
||||
"image.src =osc.toDataURL('image/png');"
|
||||
"}"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_3a_SNS_SIZE = 664;
|
||||
const char HTTP_MLX90640_3a_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x43\x33\x48\x43\xBA\x77\x8F\x68\xF7\xC4\x21\x0E\xE9\xDE\x3C"
|
||||
"\x07\x46\xC3\xA0\xF0\x58\x3A\xC2\x20\xF0\x68\xCC\xF6\xD3\x2C\x18\xFF\x75\xB9\xC8"
|
||||
"\xF8\x8F\xBF\x60\xBF\x86\xCE\xBC\x33\x7F\x3E\xCF\x06\x77\xF5\xF2\xC4\x7C\x5C\xC7"
|
||||
"\xD1\x7E\xF8\x79\x9D\xD3\xBC\x78\xF9\x61\xD3\xCC\x26\x1D\x17\x60\x8C\x83\xCE\xA7"
|
||||
"\xD5\xE3\xBA\xC7\x1D\xD3\xEC\x69\xCA\x3E\xCE\xF1\xDE\x3C\x17\xCB\x0E\x82\x30\x9D"
|
||||
"\x02\x2D\x83\xBC\x78\x31\x9F\x3B\xA6\x77\xF4\x31\x6F\x21\x99\xA7\x78\xF6\x99\xDF"
|
||||
"\xD0\x67\xC3\x93\x59\xE0\xB4\xC1\xDD\x63\x8E\xE9\xF6\x33\x34\x82\x3E\xCE\xF1\xD0"
|
||||
"\xCE\xC2\x16\xCF\x87\xC1\x87\x78\xF6\x86\x7C\x39\x5B\xA7\x83\xDC\x41\xD1\xB0\xE8"
|
||||
"\x63\x42\x08\xBD\x46\x41\x34\x7C\x47\xDF\xC7\x87\x59\xDD\x3E\xCE\xD8\x67\x6C\xFB"
|
||||
"\x3A\x81\x06\x10\x20\xC2\x38\xCE\x9C\x77\x8F\xB3\xC1\x07\x46\xC3\xA0\xE6\x3D\xBC"
|
||||
"\x43\x2E\x85\x02\x17\x09\x77\xF0\xCE\xE8\xCC\xD3\xDE\x18\x7B\xE7\xBC\x71\x9E\xF9"
|
||||
"\xDB\x67\x4D\x3A\x8E\xE1\x02\x14\xB3\x90\x81\x06\x59\xC8\x75\x33\xAA\x8F\x59\x10"
|
||||
"\xDB\xA1\x47\x42\x18\x5A\x08\x38\x81\x8D\x08\x20\x42\xC6\xCC\x67\x52\x3E\x23\xEC"
|
||||
"\xE4\x3A\x69\x0C\x36\x22\x33\x7F\x12\xFA\xCF\xB3\xC1\xB0\xF8\x32\xFF\xE6\x5B\xD4"
|
||||
"\x77\x46\x1D\xE3\xB6\x72\x10\x22\x62\x45\x4C\xD9\x47\x74\xD8\x08\x99\xF9\xC6\x7B"
|
||||
"\xE7\x6E\x10\x24\xE9\xC7\x21\xD2\x8E\xF1\xE0\x8C\xEF\xEB\xB0\x46\x8F\x88\x4C\x0C"
|
||||
"\x58\xD7\xD4\x74\x0F\xEE\xE9\x93\x09\x8D\x7D\x47\x74\xFB\x20\x8B\x4F\xB0\x41\xC1"
|
||||
"\x29\x9B\x28\x14\x70\x82\xA6\x6C\xA2\xEC\x11\x9D\xD3\xEC\x86\x16\x16\x9D\x67\xDA"
|
||||
"\x3B\x68\xD8\x8E\xDA\x3E\xCF\x34\x8F\xB4\x76\xD2\x3B\xBF\x2B\x3D\xE0\x43\xE1\x58"
|
||||
"\xE4\x3D\xF3\xD7\x74\x77\x8E\xD9\x02\x26\xE4\x7B\xE7\x78\xE9\x58\x46\x34\xD8\xC4"
|
||||
"\x3B\xA7\x28\xEF\x1D\xC3\xB6\x77\x0F\xB3\xB8\x53\x1D\x99\x79\x06\xAE\x91\x0C\xCF"
|
||||
"\x1E\x68\xFB\x47\x6C\x11\x78\x1D\x47\x6D\x1F\x68\xEA\x04\x7A\x07\x21\xEF\x9D\xE3"
|
||||
"\xC1\x76\x08\xCE\x96\x30\x63\xE1\x08\x31\x5A\x10\x87\x74\x10\xF8\x03\x3B\xC7\x80"
|
||||
"\x43\xE5\xA0\x12\x2D\x82\x0D\x73\xDC\x7B\x8D";
|
||||
#define HTTP_MLX90640_3a_SNS Decompress(HTTP_MLX90640_3a_SNS_COMPRESSED,HTTP_MLX90640_3a_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_3a_SNS[] PROGMEM =
|
||||
"function poiD(){" //poi draw
|
||||
"grdD();"
|
||||
"ctx.globalCompositeOperation = 'source-over';"
|
||||
"var rO = new Range();" //rangeObject
|
||||
"rO.selectNodeContents(eb('m2'));"
|
||||
"rO.deleteContents();"
|
||||
"for(var p in poi){"
|
||||
//"console.log('poi:'+ poi[p][0]);"
|
||||
"var c=150;"
|
||||
"if(eb('poiL').value==p){c=255;}"
|
||||
// "console.log(c);"
|
||||
"ctx.fillStyle = 'rgba('+c+','+c+','+c+',0.6)';"
|
||||
"ctx.beginPath();"
|
||||
"ctx.arc(poi[p][0]+0.5, poi[p][1]+0.5,1,0,2*Math.PI);"
|
||||
"ctx.fill();"
|
||||
"ctx.font = '1.5px Verdana';"
|
||||
"x=parseInt(p)+1;"
|
||||
"ctx.fillText(x, poi[p][0]+1.5, poi[p][1]+1.2);"
|
||||
"var node = document.createElement('LI');"
|
||||
"var textnode = document.createTextNode('POI-' + x + ': ' + (rA[(poi[p][1]*32)+poi[p][0]]).toFixed(2) + ' °C at Pos: ' + poi[p][0] + ' , ' + poi[p][1]);"
|
||||
"node.appendChild(textnode);"
|
||||
"eb('m2').appendChild(node);"
|
||||
"}"
|
||||
"}"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_3b_SNS_SIZE = 477;
|
||||
const char HTTP_MLX90640_3b_SNS_COMPRESSED[] PROGMEM = "\x30\x2F\x83\xAD\xCE\x5E\x74\x2C\x61\xDD\x3B\xC7\xB7\xE1\x1D\xD3\xEC\x58\xC3\xEC"
|
||||
"\xEA\x38\xCE\xF1\xE0\x83\xBE\x33\xBF\x23\xE2\x63\x8E\xE9\xF6\x34\x23\x61\xF6\x77"
|
||||
"\x8F\x01\xD1\xB1\x1F\x10\x20\xD5\x3A\x0F\x3A\x20\xF3\xA9\x9B\x28\xEE\x9F\x67\x28"
|
||||
"\x84\x3E\xC1\x0F\x0D\x3A\x58\x82\x13\x33\x7D\x44\x16\xFA\x9F\x3F\x9D\xD3\xEC\x6E"
|
||||
"\x0B\xF3\x1B\x86\x6C\xFB\x3A\x90\x21\xE9\xC7\x75\x99\xD1\xDE\x47\xB4\xCE\xFE\x86"
|
||||
"\x90\xC4\x7C\x43\xCE\x88\x6E\x0B\xF3\x21\x99\xE3\xBA\x08\x39\x29\xD4\x99\x9D\x1D"
|
||||
"\xE3\xC3\x1C\x77\x4F\xB1\xA7\x21\xF6\x77\x8E\x85\xBD\xCF\xE4\x28\xA8\x86\x90\x48"
|
||||
"\xF8\x8F\xB2\xA6\x34\x63\xFD\xD0\xBF\xB3\xCD\x1F\x68\xED\xA3\xBB\xF2\xB3\xDE\x3B"
|
||||
"\xA3\x48\x61\xD0\xC8\xF5\x9C\xBA\x3B\xC7\x6C\x86\x90\xC3\xA1\xB0\xF7\xCE\x95\x84"
|
||||
"\x63\x4D\x8C\x43\xBA\x72\x8E\xF1\xDE\x3B\x87\x6C\xEE\x1F\x67\x70\xAE\x91\x0C\xCF"
|
||||
"\x02\x0E\x18\x34\x86\x1D\x0D\x88\xED\xA3\xED\x1D\x40\x87\x2C\xC8\xF0\x7B\x81\x6F"
|
||||
"\x82\x01\x30\x7F\x81\x4B\x82\x02\x10\x15\xF8\x20\x33\xBF\xAD\x10\xD8\x08\x5C\x54"
|
||||
"\x0C\xCD\x20\x8F\xB3\xBC\x74\x33\xB0\x85\xB3\xC0\xCC\xD3\xDE\xD1\x0D\x87\xBE\x7B"
|
||||
"\xC7\x19\xEF\x9F\x08\x69\x08\x74\x36\x02\x2C\xD3\x90\xF7\xCF\x84\x34\x84\x3A\x19"
|
||||
"\x1E\x06\xE7\x8E\xE9\xDE\x3C\x02\x1F\x1E\xA0\x97\x8E\x9E\xB3\x91\xB6\xCE\xD9\xDD"
|
||||
"\x21\xA4\x21\xD0\xD8\x7A\xCE\x46\xCE\xF1\xDB\x21\xA4\x21\xD0\xC8\xEF\x1E\x0F\x71"
|
||||
"\xDE\x3C\x1B";
|
||||
#define HTTP_MLX90640_3b_SNS Decompress(HTTP_MLX90640_3b_SNS_COMPRESSED,HTTP_MLX90640_3b_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_3b_SNS[] PROGMEM =
|
||||
"function setup(){"
|
||||
"rl('up',0);"
|
||||
"canvas = eb('mlx');"
|
||||
"ctx = canvas.getContext('2d');"
|
||||
"canvas.addEventListener('mousemove', function(evt) {"
|
||||
"var mP = getMousePos(canvas, evt);"
|
||||
// console.log((mP.y*32)+mP.x);
|
||||
"eb('m1').innerHTML = 'Temperature: ' + (rA[(mP.y*32)+mP.x].toFixed(2)) + ' at Pos: ' + mP.x + ' , ' + mP.y;"
|
||||
"});"
|
||||
"canvas.addEventListener('mousedown', function(evt) {"
|
||||
"var mD = getMousePos(canvas, evt);"
|
||||
"var idx = eb('poiL').value;"
|
||||
"poi[idx][0]=mD.x;"
|
||||
"poi[idx][1]=mD.y;"
|
||||
//"console.log(poi);"
|
||||
"mos();"
|
||||
"rl('up',eb('poiL').value*10000+(mD.x*100)+mD.y);" //poi-1: 2,14 -> 10214
|
||||
"});"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_4a_SNS_SIZE = 468;
|
||||
const char HTTP_MLX90640_4a_SNS_COMPRESSED[] PROGMEM = "\xD3\x08\xEE\x87\x7C\x67\x7E\x3A\x0F\x3A\x20\xF3\xA9\x9B\x28\xEF\x23\xDA\x1D\x1B"
|
||||
"\x0E\x9E\x0E\xC2\x67\x74\xE4\x67\x54\x67\x78\xF0\x41\xD1\xB0\xE9\xA3\x6C\x79\x97"
|
||||
"\x86\xE6\x50\xAD\xE1\xE2\x7D\x63\x82\x62\x23\xE2\xAF\x8B\x67\x81\xEF\x88\x8F\x88"
|
||||
"\x3A\x36\x1D\x03\xFB\xBA\x64\x16\xF3\xBF\x90\xF7\xEC\x4D\x7D\x47\x74\xE3\x3A\x8E"
|
||||
"\xE3\x3A\x8E\xE3\x3A\x8E\xE5\x61\xDE\x3C\x10\xF7\xC4\x3A\x58\x82\x10\x78\x16\x7C"
|
||||
"\xBD\x58\x30\xEE\x9C\x67\x51\xDC\x3E\xC8\xC9\x84\x16\x0F\x9F\x60\x9D\x68\xE8\x71"
|
||||
"\xFB\x4E\xA3\xB8\x7D\x96\x7E\xF8\x79\x82\x85\xD3\x95\xA7\x51\xDC\x3E\xC8\xCF\x70"
|
||||
"\x27\x40\xA1\x70\xE6\x69\xD4\x77\x0F\xB2\x12\xFE\x68\x38\x21\x60\xA1\x8F\x1C\x87"
|
||||
"\x51\xDC\x3E\xC8\x70\x56\x19\xA0\x20\xD9\x21\x0E\xE9\xDE\x3C\x0F\x10\xC3\x60\x21"
|
||||
"\x70\x5A\x3C\xE8\xB4\x6D\x8F\x32\x12\xEA\xCE\xE9\xCB\xAD\x3A\x8E\xE3\x3A\x8E\xE4"
|
||||
"\x3A\xAA\xD1\xDE\x3C\x10\xDC\xF1\xDD\x3B\xC7\x8D\x1B\x63\xCC\xE9\x9C\x16\x58\x88"
|
||||
"\xF8\x8C\x0B\xE0\xEB\x73\x91\xDD\x04\x2E\x29\xC4\xFD\x8F\x96\x8D\xB1\xE6\x77\x74"
|
||||
"\x6D\x8F\x33\xA8\xE3\x3A\x99\xD5\x74\x75\x56\x1D\xE3\xC1\x0C\xCD\x21\x0E\xE9\xDE"
|
||||
"\x3C\x1E\xE3\xDC\x7B\x81\x13\x12\x04\x1D\x74\xF6\x87\x46\xC3\xA1\x8D\x08\x22\xF5"
|
||||
"\x19\x04\xD1\xF1\x0F\x7C\x43\xC0\x21\xD0\x2F\xB0\xE8\xEE\x9C\xBA\x20\x41\xE2\xB8"
|
||||
"\xEA\x39\x58\x77\x8F\x07\xB8\xF4\x3B\x0B\xC1\xFF\x46\x51\xF8";
|
||||
#define HTTP_MLX90640_4a_SNS Decompress(HTTP_MLX90640_4a_SNS_COMPRESSED,HTTP_MLX90640_4a_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_4a_SNS[] PROGMEM =
|
||||
"if (canvas.getContext) {"
|
||||
"ctx.scale(10,10);"
|
||||
"ctx.imageSmoothingEnabled = true;"
|
||||
"grd = ctx.createLinearGradient(0, 0, 0, 24);" // gradient
|
||||
"grd.addColorStop(0, 'yellow');"
|
||||
"grd.addColorStop(.075, 'orange');"
|
||||
"grd.addColorStop(.25, 'violet');"
|
||||
"grd.addColorStop(.45, 'darkblue');"
|
||||
"grd.addColorStop(1, 'black');"
|
||||
"grdD();"
|
||||
"gPx = ctx.getImageData(325, 0, 1,239);"
|
||||
"mos();"
|
||||
"image.onload = function () {"
|
||||
"ctx.drawImage(image,0,0,32,24);"
|
||||
"poiD();"
|
||||
"}"
|
||||
"}"
|
||||
"}"
|
||||
"function grdD(){" //gradient draw()
|
||||
"ctx.fillStyle = grd;"
|
||||
"ctx.fillRect(32, 0, 2,24);}"
|
||||
"</script>"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
#ifdef USE_UNISHOX_COMPRESSION
|
||||
const size_t HTTP_MLX90640_4b_SNS_SIZE = 418;
|
||||
const char HTTP_MLX90640_4b_SNS_COMPRESSED[] PROGMEM = "\x3D\x07\x60\x86\x4B\x38\x2C\xB1\x0F\x87\xDF\x9D\x0B\x18\x77\x4E\xF1\xE0\xFB\x3F"
|
||||
"\x0F\x40\xEF\x8C\xEF\xCB\x44\x3E\x1F\x63\x42\x36\x1F\x68\x7F\x44\xA1\x47\xC3\xEC"
|
||||
"\xE5\xE3\x3E\xCE\xE1\x0A\x7A\x3C\x2A\x2B\x8F\x87\xD9\xCA\xC6\x7D\x9F\x87\xA1\xD8"
|
||||
"\x40\x83\x83\x9F\x87\xA0\x9A\x66\x7E\x1E\x87\x60\x9A\x66\x7E\x1E\x9E\x61\x30\xE9"
|
||||
"\x68\x87\xC3\xEC\x66\x69\x04\x7D\xAC\xE0\xC5\x5F\x0F\x33\xE1\xF6\x37\x3C\x77\x4E"
|
||||
"\xF1\xF6\x7E\x1E\x98\x32\xB7\x39\x19\xD8\x42\xD9\xF0\xFB\x38\xCF\xB3\xF0\x88\x61"
|
||||
"\x61\x69\xD6\x72\x1E\x87\x61\x02\x0D\x40\x4B\xB8\x72\x10\x20\xDC\x39\x44\x0A\x77"
|
||||
"\x0E\x51\x02\x0D\xC3\x96\x40\xA7\x70\xE5\x90\x20\xDC\x39\x84\x0A\x77\x0E\x61\x02"
|
||||
"\x0D\xC3\x9A\x40\xA7\x70\xE6\x90\x20\xDC\x39\xC4\x08\xB7\x0E\xC0\x41\xE1\x2A\x01"
|
||||
"\xFC\x3D\x04\xD3\x30\x41\xE2\x0C\xE4\x3E\xC8\x10\xF8\x5B\x13\x4C\xCF\xC2\x18\x58"
|
||||
"\x5A\x75\x9C\x67\x99\xDC\x3D\x0B\xC3\x2F\x96\x88\x7C\x3E\xEC\xE4\x3E\xCF\xC3\xD0"
|
||||
"\xEC\x2F\x0C\xBE\x3F\x26\x3B\x32\xF2\x0D\x1D\xDF\x3E\xF6\x7C\xEF\x02\x2E\x1E\x08"
|
||||
"\x39\x11\xCA\x20\x44\xC8\x8E\xC1\xD8\x21\x91\xF8";
|
||||
#define HTTP_MLX90640_4b_SNS Decompress(HTTP_MLX90640_4b_SNS_COMPRESSED,HTTP_MLX90640_4b_SNS_SIZE).c_str()
|
||||
#else
|
||||
const char HTTP_MLX90640_4b_SNS[] PROGMEM =
|
||||
"<body onload='setup();'>"
|
||||
"<canvas id='mlx' width='340' height='240'></canvas>"
|
||||
"<div></div>"
|
||||
"<select id='poiL' onchange='mos()'>"
|
||||
"<option value='0'>POI-1</option>"
|
||||
"<option value='1'>POI-2</option>"
|
||||
"<option value='2'>POI-3</option>"
|
||||
"<option value='3'>POI-4</option>"
|
||||
"<option value='4'>POI-5</option>"
|
||||
"<option value='5'>POI-6</option>"
|
||||
"</select>"
|
||||
"<div id='m1'></div>"
|
||||
"<div>POI-0: <span id='a1'></span>°C (sensor)</div>"
|
||||
"<div id='m2'></div>"
|
||||
"</body>"
|
||||
;
|
||||
#endif //USE_UNISHOX_COMPRESSION
|
||||
void MLX90640UpdateGUI(void){
|
||||
WSContentStart_P("mlx");
|
||||
WSContentSendStyle();
|
||||
WSContentSend_P(HTTP_MLX90640_1_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_2a_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_2b_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_3a_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_3b_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_4a_SNS);
|
||||
WSContentSend_P(HTTP_MLX90640_4b_SNS);
|
||||
WSContentSpaceButton(BUTTON_MAIN);
|
||||
WSContentStop();
|
||||
}
|
||||
|
||||
void MLX90640HandleWebGuiResponse(void){
|
||||
char tmp[(MLX90640_POI_NUM*2)+4];
|
||||
WebGetArg("ul", tmp, sizeof(tmp)); // update line
|
||||
if (strlen(tmp)) {
|
||||
uint8_t _line = atoi(tmp);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, "MLX90640: send line %u", _line);
|
||||
float _buf[65];
|
||||
if(_line==0){_buf[0]=1000+MLX90640.Ta;} //ambient temperature modulation hack
|
||||
else{_buf[0]=(float)_line;}
|
||||
memcpy((char*)&_buf[1],(char*)&MLX90640.To[_line*64],64*4);
|
||||
Webserver->send(200,PSTR("application/octet-stream"),(const char*)&_buf,65*4);
|
||||
return;
|
||||
}
|
||||
WebGetArg("up", tmp, sizeof(tmp)); // update POI to browser
|
||||
if (strlen(tmp)==1) {
|
||||
Webserver->send(200,PSTR("application/octet-stream"),(const char*)&MLX90640.pois,MLX90640_POI_NUM*2);
|
||||
return;
|
||||
}
|
||||
else if (strlen(tmp)>2) { // receive updated POI from browser
|
||||
uint32_t _poi = atoi(tmp);
|
||||
uint32_t _poiNum = (_poi-(_poi%10000))/10000;
|
||||
MLX90640.pois[_poiNum*2] = (_poi%10000)/100;
|
||||
MLX90640.pois[(_poiNum*2)+1] = _poi%100;
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RAW: %u, POI-%u: x: %u, y: %u"),_poi,_poiNum,MLX90640.pois[_poiNum],MLX90640.pois[_poiNum+1]);
|
||||
for(int i = 0;i<MLX90640_POI_NUM;i++){
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("POI-%u: x: %u, y: %u"),i+1,MLX90640.pois[i*2],MLX90640.pois[(i*2)+1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MLX90640HandleWebGui(void){
|
||||
if (!HttpCheckPriviledgedAccess()) { return; }
|
||||
MLX90640HandleWebGuiResponse();
|
||||
MLX90640UpdateGUI();
|
||||
}
|
||||
#endif // USE_WEBSERVER
|
||||
|
||||
/************************************************************************\
|
||||
* Command
|
||||
\************************************************************************/
|
||||
bool MLX90640Cmd(void){
|
||||
char command[CMDSZ];
|
||||
bool serviced = true;
|
||||
uint8_t disp_len = strlen(D_CMND_MLX90640);
|
||||
|
||||
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_MLX90640), disp_len)) { // prefix
|
||||
int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + disp_len, kMLX90640_Commands);
|
||||
uint32_t _idx;
|
||||
switch (command_code) {
|
||||
case CMND_MLX90640_POI:
|
||||
if(XdrvMailbox.index>(MLX90640_POI_NUM-1)&&XdrvMailbox.index<1) return false;
|
||||
_idx = (XdrvMailbox.index-1)*2;
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
uint32_t _coord = TextToInt(XdrvMailbox.data);
|
||||
MLX90640.pois[_idx] = (_coord%10000)/100;
|
||||
if(MLX90640.pois[_idx]>31) MLX90640.pois[_idx]=31;
|
||||
MLX90640.pois[_idx+1] = _coord%100;
|
||||
if(MLX90640.pois[_idx+1]>23) MLX90640.pois[_idx+1]=23;
|
||||
}
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("POI-%u = x:%u,y:%u"),XdrvMailbox.index,MLX90640.pois[_idx],MLX90640.pois[_idx+1]);
|
||||
Response_P(S_JSON_MLX90640_COMMAND_NVALUE, command, XdrvMailbox.payload);
|
||||
break;
|
||||
default:
|
||||
// else for Unknown command
|
||||
serviced = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return serviced;
|
||||
}
|
||||
|
||||
/************************************************************************\
|
||||
* Init
|
||||
\************************************************************************/
|
||||
void MLX90640init()
|
||||
{
|
||||
if (MLX90640.type || !I2cSetDevice(MLX90640_ADDRESS)) { return; }
|
||||
|
||||
Wire.setClock(400000);
|
||||
int status = -1;
|
||||
if(!MLX90640.dumpedEE){
|
||||
status = MLX90640_DumpEE(MLX90640_ADDRESS, MLX90640.Frame);
|
||||
if (status != 0){
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("Failed to load system parameters"));
|
||||
}
|
||||
else {
|
||||
AddLog_P2(LOG_LEVEL_INFO, PSTR("MLX90640: started"));
|
||||
MLX90640.type = true;
|
||||
}
|
||||
MLX90640.params = new paramsMLX90640;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************\
|
||||
* Run loop
|
||||
\************************************************************************/
|
||||
void MLX90640every100msec(){
|
||||
static uint32_t _job = 0;
|
||||
int status;
|
||||
uint32_t _time;
|
||||
|
||||
if(!MLX90640.extractedParams){
|
||||
static uint32_t _chunk = 0;
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: will read chunk: %u"), _chunk);
|
||||
_time = millis();
|
||||
status = MLX90640_ExtractParameters(MLX90640.Frame, MLX90640.params, _chunk);
|
||||
if (status == 0){
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: parameter received after: %u msec, status: %u"), TimePassedSince(_time), status);
|
||||
}
|
||||
if (_chunk == 5) MLX90640.extractedParams = true;
|
||||
_chunk++;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(_job){
|
||||
case 0:
|
||||
if(MLX90640_SynchFrame(MLX90640_ADDRESS)!=0){
|
||||
_job=-1;
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: frame not ready"));
|
||||
break;
|
||||
}
|
||||
// _time = millis();
|
||||
status = MLX90640_GetFrameData(MLX90640_ADDRESS, MLX90640.Frame);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: got frame 0 in %u msecs, status: %i"), TimePassedSince(_time), status);
|
||||
break;
|
||||
case 1:
|
||||
MLX90640.Ta = MLX90640_GetTa(MLX90640.Frame, MLX90640.params);
|
||||
break;
|
||||
case 2:
|
||||
// _time = millis();
|
||||
MLX90640_CalculateTo(MLX90640.Frame, MLX90640.params, 0.95f, MLX90640.Ta - 8, MLX90640.To, 0);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: calculated temperatures in %u msecs"), TimePassedSince(_time));
|
||||
break;
|
||||
case 5:
|
||||
if(MLX90640_SynchFrame(MLX90640_ADDRESS)!=0){
|
||||
_job=4;
|
||||
break;
|
||||
}
|
||||
// _time = millis();
|
||||
status = MLX90640_GetFrameData(MLX90640_ADDRESS, MLX90640.Frame);
|
||||
// // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: got frame 1 in %u msecs, status: %i"), TimePassedSince(_time), status);
|
||||
break;
|
||||
case 7:
|
||||
// _time = millis();
|
||||
MLX90640_CalculateTo(MLX90640.Frame, MLX90640.params, 0.95f, MLX90640.Ta - 8, MLX90640.To, 1);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("MLX90640: calculated temperatures in %u msecs"), TimePassedSince(_time));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_job++;
|
||||
if(_job>10) _job=0;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
void MLX90640Show(uint8_t json)
|
||||
{
|
||||
char amb_tstr[FLOATSZ];
|
||||
dtostrfd(MLX90640.Ta, Settings.flag2.temperature_resolution, amb_tstr);
|
||||
if (json) {
|
||||
ResponseAppend_P(PSTR(",\"MLX90640\":{\"" D_JSON_TEMPERATURE "\":[%s"), amb_tstr);
|
||||
for(int i = 0;i<MLX90640_POI_NUM;i++){
|
||||
char obj_tstr[FLOATSZ];
|
||||
dtostrfd(MLX90640.To[MLX90640.pois[i*2]+(MLX90640.pois[(i*2)+1]*32)], Settings.flag2.temperature_resolution, obj_tstr);
|
||||
ResponseAppend_P(PSTR(",%s"),obj_tstr);
|
||||
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Array pos: %u"),MLX90640.pois[i*2]+(MLX90640.pois[(i*2)+1]*32));
|
||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("POI-%u: x: %u, y: %u"),i+1,MLX90640.pois[i*2],MLX90640.pois[(i*2)+1]);
|
||||
}
|
||||
ResponseAppend_P(PSTR("]}"));
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xdrv43(uint8_t function)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (FUNC_INIT == function) {
|
||||
MLX90640init();
|
||||
}
|
||||
if(MLX90640.type){
|
||||
switch (function) {
|
||||
case FUNC_EVERY_100_MSECOND:
|
||||
MLX90640every100msec();
|
||||
break;
|
||||
case FUNC_JSON_APPEND:
|
||||
MLX90640Show(1);
|
||||
break;
|
||||
#ifdef USE_WEBSERVER
|
||||
case FUNC_WEB_ADD_MAIN_BUTTON:
|
||||
WSContentSend_P(HTTP_BTN_MENU_MLX90640);
|
||||
break;
|
||||
case FUNC_WEB_ADD_HANDLER:
|
||||
Webserver->on("/mlx", MLX90640HandleWebGui);
|
||||
break;
|
||||
#endif // USE_WEBSERVER
|
||||
case FUNC_COMMAND:
|
||||
result = MLX90640Cmd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_MLX90640_SENSOR
|
||||
#endif // USE_I2C
|
|
@ -613,15 +613,17 @@ void BmpShow(bool json)
|
|||
|
||||
void BMP_EnterSleep(void)
|
||||
{
|
||||
for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) {
|
||||
switch (bmp_sensors[bmp_idx].bmp_type) {
|
||||
case BMP180_CHIPID:
|
||||
case BMP280_CHIPID:
|
||||
case BME280_CHIPID:
|
||||
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP_REGISTER_RESET, BMP_CMND_RESET);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (DeepSleepEnabled()) {
|
||||
for (uint32_t bmp_idx = 0; bmp_idx < bmp_count; bmp_idx++) {
|
||||
switch (bmp_sensors[bmp_idx].bmp_type) {
|
||||
case BMP180_CHIPID:
|
||||
case BMP280_CHIPID:
|
||||
case BME280_CHIPID:
|
||||
I2cWrite8(bmp_sensors[bmp_idx].bmp_address, BMP_REGISTER_RESET, BMP_CMND_RESET);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -227,7 +227,7 @@ a_features = [[
|
|||
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
|
||||
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
|
||||
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_I2S_AUDIO",
|
||||
"","","","",
|
||||
"USE_MLX90640","","","",
|
||||
"","USE_TTGO_WATCH","USE_ETHERNET","USE_WEBCAM"
|
||||
],[
|
||||
"","","","",
|
||||
|
|
Loading…
Reference in New Issue