Merge remote-tracking branch 'remotes/upstream/development' into development

This commit is contained in:
Chris Keydel 2020-09-15 09:40:36 +02:00
commit 51688c18ea
33 changed files with 6541 additions and 3445 deletions

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

@ -7,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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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
};

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -148,7 +148,7 @@ const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007), ZLE(0x0008) }; // Hue, Sat, X, Y, CT, ColorMode
// This callback is registered after a cluster specific command and sends a read command for the same cluster
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
void Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
size_t attrs_len = 0;
const uint8_t* attrs = nullptr;
@ -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:

View File

@ -533,7 +533,11 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
#endif
for (uint32_t i = 0; i < activeEpCount; i++) {
zigbee_devices.addEndpoint(nwkAddr, activeEpList[i]);
uint8_t ep = activeEpList[i];
zigbee_devices.addEndpoint(nwkAddr, ep);
if ((i < 4) && (ep < 0x10)) {
zigbee_devices.queueTimer(nwkAddr, 0 /* groupaddr */, 1500, ep /* fake cluster as ep */, ep, Z_CAT_EP_DESC, 0 /* value */, &Z_SendSimpleDescReq);
}
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
@ -546,7 +550,132 @@ int32_t Z_ReceiveActiveEp(int32_t res, const class SBuffer &buf) {
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
Z_SendAFInfoRequest(nwkAddr); // probe for ModelId and ManufId
Z_SendDeviceInfoRequest(nwkAddr); // probe for ModelId and ManufId
return -1;
}
// list of clusters that need bindings
const uint8_t Z_bindings[] PROGMEM = {
Cx0001, Cx0006, Cx0008, Cx0300,
Cx0400, Cx0402, Cx0403, Cx0405, Cx0406,
Cx0500,
};
int32_t Z_ClusterToCxBinding(uint16_t cluster) {
uint8_t cx = ClusterToCx(cluster);
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (pgm_read_byte(&Z_bindings[i]) == cx) {
return i;
}
}
return -1;
}
void Z_AutoBindDefer(uint16_t shortaddr, uint8_t endpoint, const SBuffer &buf,
size_t in_index, size_t in_len, size_t out_index, size_t out_len) {
// We use bitmaps to mark clusters that need binding and config attributes
// All clusters in 'in' and 'out' are bounded
// Only cluster in 'in' receive configure attribute requests
uint32_t cluster_map = 0; // max 32 clusters to bind
uint32_t cluster_in_map = 0; // map of clusters only in 'in' group, to be bounded
// First enumerate all clusters to bind, from in or out clusters
// scan in clusters
for (uint32_t i=0; i<in_len; i++) {
uint16_t cluster = buf.get16(in_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
bitSet(cluster_in_map, found_cx);
}
}
// scan out clusters
for (uint32_t i=0; i<out_len; i++) {
uint16_t cluster = buf.get16(out_index + i*2);
uint32_t found_cx = Z_ClusterToCxBinding(cluster); // convert to Cx of -1 if not found
if (found_cx >= 0) {
bitSet(cluster_map, found_cx);
}
}
// if IAS device, request the device type
if (bitRead(cluster_map, Z_ClusterToCxBinding(0x0500))) {
// send a read command to cluster 0x0500, attribute 0x0001 (ZoneType) - to read the type of sensor
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, 0x0500, endpoint, Z_CAT_READ_ATTRIBUTE, 0x0001, &Z_SendSingleAttributeRead);
}
// enqueue bind requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_BIND, 0 /* value */, &Z_AutoBind);
}
}
// enqueue config attribute requests
for (uint32_t i=0; i<ARRAY_SIZE(Z_bindings); i++) {
if (bitRead(cluster_in_map, i)) {
uint16_t cluster = CxToCluster(pgm_read_byte(&Z_bindings[i]));
zigbee_devices.queueTimer(shortaddr, 0 /* groupaddr */, 2000, cluster, endpoint, Z_CAT_CONFIG_ATTR, 0 /* value */, &Z_AutoConfigReportingForCluster);
}
}
}
int32_t Z_ReceiveSimpleDesc(int32_t res, const class SBuffer &buf) {
#ifdef USE_ZIGBEE_ZNP
// Received ZDO_SIMPLE_DESC_RSP
// Z_ShortAddress srcAddr = buf.get16(2);
uint8_t status = buf.get8(4);
Z_ShortAddress nwkAddr = buf.get16(5);
uint8_t lenDescriptor = buf.get8(7);
uint8_t endpoint = buf.get8(8);
uint16_t profileId = buf.get16(9); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(11); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(13); // 0 Version 1.00
uint8_t numInCluster = buf.get8(14);
uint8_t numOutCluster = buf.get8(15 + numInCluster*2);
const size_t numInIndex = 15;
const size_t numOutIndex = 16 + numInCluster*2;
#endif
#ifdef USE_ZIGBEE_EZSP
uint8_t status = buf.get8(0);
Z_ShortAddress nwkAddr = buf.get16(1);
uint8_t lenDescriptor = buf.get8(3);
uint8_t endpoint = buf.get8(4);
uint16_t profileId = buf.get16(5); // The profile Id for this endpoint.
uint16_t deviceId = buf.get16(7); // The Device Description Id for this endpoint.
uint8_t deviceVersion = buf.get8(9); // 0 Version 1.00
uint8_t numInCluster = buf.get8(10);
uint8_t numOutCluster = buf.get8(11 + numInCluster*2);
const size_t numInIndex = 11;
const size_t numOutIndex = 12 + numInCluster*2;
#endif
if (0 == status) {
if (!Settings.flag4.zb_disable_autobind) {
Z_AutoBindDefer(nwkAddr, endpoint, buf, numInIndex, numInCluster, numOutIndex, numOutCluster);
}
Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
"\"Status\":%d,\"Endpoint\":\"0x%02X\""
",\"ProfileId\":\"0x%04X\",\"DeviceId\":\"0x%04X\",\"DeviceVersion\":%d"
"\"InClusters\":["),
ZIGBEE_STATUS_SIMPLE_DESC, endpoint,
profileId, deviceId, deviceVersion);
for (uint32_t i = 0; i < numInCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numInIndex + i*2));
}
ResponseAppend_P(PSTR("],\"OutClusters\":["));
for (uint32_t i = 0; i < numOutCluster; i++) {
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"0x%04X\""), buf.get16(numOutIndex + i*2));
}
ResponseAppend_P(PSTR("]}}"));
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
}
return -1;
}
@ -831,7 +960,7 @@ int32_t Z_MgmtBindRsp(int32_t res, const class SBuffer &buf) {
//uint64_t srcaddr = buf.get16(idx); // unused
uint8_t srcep = buf.get8(idx + 8);
uint8_t cluster = buf.get16(idx + 9);
uint16_t cluster = buf.get16(idx + 9);
uint8_t addrmode = buf.get8(idx + 11);
uint16_t group = 0x0000;
uint64_t dstaddr = 0;
@ -960,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()
}));
}
}

View File

@ -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

View File

@ -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) ||

View File

@ -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 {

View File

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

View File

@ -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

View File

@ -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;
}
}
}
}

View File

@ -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"
],[
"","","","",