mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-28 05:06:32 +00:00
Merge branch 'development' into pre-release-850
This commit is contained in:
commit
fe3f705448
10
BUILDS.md
10
BUILDS.md
@ -10,6 +10,7 @@
|
|||||||
| USE_MQTT_TLS_CA_CERT | - | - | - | - | - | - | - |
|
| USE_MQTT_TLS_CA_CERT | - | - | - | - | - | - | - |
|
||||||
| USE_MQTT_AWS_IOT | - | - | - | - | - | - | - |
|
| USE_MQTT_AWS_IOT | - | - | - | - | - | - | - |
|
||||||
| USE_4K_RSA | - | - | - | - | - | - | - |
|
| USE_4K_RSA | - | - | - | - | - | - | - |
|
||||||
|
| USE_TELEGRAM | - | - | - | - | - | - | - |
|
||||||
| USE_KNX | - | - | - | x | - | - | - |
|
| USE_KNX | - | - | - | x | - | - | - |
|
||||||
| USE_WEBSERVER | x | x | x | x | x | x | x |
|
| USE_WEBSERVER | x | x | x | x | x | x | x |
|
||||||
| USE_JAVASCRIPT_ES6 | - | - | - | - | - | - | - |
|
| USE_JAVASCRIPT_ES6 | - | - | - | - | - | - | - |
|
||||||
@ -69,7 +70,9 @@
|
|||||||
| USE_DDSU666 | - | - | - | - | x | - | - |
|
| USE_DDSU666 | - | - | - | - | x | - | - |
|
||||||
| USE_SOLAX_X1 | - | - | - | - | - | - | - |
|
| USE_SOLAX_X1 | - | - | - | - | - | - | - |
|
||||||
| USE_LE01MR | - | - | - | - | - | - | - |
|
| USE_LE01MR | - | - | - | - | - | - | - |
|
||||||
|
| USE_BL0940 | - | x | x | x | x | - | - |
|
||||||
| USE_TELEINFO | - | - | - | - | - | - | - |
|
| USE_TELEINFO | - | - | - | - | - | - | - |
|
||||||
|
| USE_IEM3000 | - | - | - | - | - | - | - |
|
||||||
| | | | | | | | |
|
| | | | | | | | |
|
||||||
| USE_ADC_VCC | x | x | - | - | - | x | - |
|
| USE_ADC_VCC | x | x | - | - | - | x | - |
|
||||||
| USE_COUNTER | - | - | x | x | x | - | x |
|
| USE_COUNTER | - | - | x | x | x | - | x |
|
||||||
@ -126,6 +129,7 @@
|
|||||||
| USE_VEML6075 | - | - | - | - | - | - | - |
|
| USE_VEML6075 | - | - | - | - | - | - | - |
|
||||||
| USE_VEML7700 | - | - | - | - | - | - | - |
|
| USE_VEML7700 | - | - | - | - | - | - | - |
|
||||||
| USE_MCP9808 | - | - | - | - | - | - | - |
|
| USE_MCP9808 | - | - | - | - | - | - | - |
|
||||||
|
| USE_HP303B | - | - | - | - | - | - | - |
|
||||||
| | | | | | | | |
|
| | | | | | | | |
|
||||||
| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
|
| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
|
||||||
| USE_SPI | - | - | - | - | - | - | x |
|
| USE_SPI | - | - | - | - | - | - | x |
|
||||||
@ -143,8 +147,9 @@
|
|||||||
| USE_GPS | - | - | - | - | - | - | - |
|
| USE_GPS | - | - | - | - | - | - | - |
|
||||||
| USE_HM10 | - | - | - | - | x | - | - |
|
| USE_HM10 | - | - | - | - | x | - | - |
|
||||||
| USE_HRXL | - | - | - | - | x | - | - |
|
| USE_HRXL | - | - | - | - | x | - | - |
|
||||||
| USE_TASMOTA_SLAVE | - | - | - | - | - | - | - |
|
| USE_TASMOTA_CLIENT | - | - | - | - | - | - | - |
|
||||||
| USE_OPENTHERM | - | - | - | - | - | - | - |
|
| USE_OPENTHERM | - | - | - | - | - | - | - |
|
||||||
|
| USE_TCP_BRIDGE | - | - | - | - | - | - | - | zbbridge
|
||||||
| | | | | | | | |
|
| | | | | | | | |
|
||||||
| USE_NRF24 | - | - | - | - | - | - | - |
|
| USE_NRF24 | - | - | - | - | - | - | - |
|
||||||
| USE_MIBLE | - | - | - | - | - | - | - |
|
| USE_MIBLE | - | - | - | - | - | - | - |
|
||||||
@ -157,6 +162,7 @@
|
|||||||
| USE_IR_REMOTE_FULL | - | - | - | - | - | x | - | Enable ALL protocols
|
| USE_IR_REMOTE_FULL | - | - | - | - | - | x | - | Enable ALL protocols
|
||||||
| | | | | | | | |
|
| | | | | | | | |
|
||||||
| USE_SR04 | - | - | - | - | x | - | - |
|
| USE_SR04 | - | - | - | - | x | - | - |
|
||||||
|
| USE_DYP | - | - | - | - | - | - | - |
|
||||||
| USE_TM1638 | - | - | - | - | x | - | - |
|
| USE_TM1638 | - | - | - | - | x | - | - |
|
||||||
| USE_HX711 | - | - | - | - | x | - | - |
|
| USE_HX711 | - | - | - | - | x | - | - |
|
||||||
| USE_TX2x_WIND_SENSOR | - | - | - | - | - | - | - |
|
| USE_TX2x_WIND_SENSOR | - | - | - | - | - | - | - |
|
||||||
@ -186,3 +192,5 @@
|
|||||||
| USE_MI_ESP32 | - | - | - | - | - | - | - | - |
|
| USE_MI_ESP32 | - | - | - | - | - | - | - | - |
|
||||||
| USE_WEBCAM | - | - | - | - | - | - | - | x |
|
| USE_WEBCAM | - | - | - | - | - | - | - | x |
|
||||||
| USE_ETHERNET | - | - | - | - | - | - | - | - |
|
| USE_ETHERNET | - | - | - | - | - | - | - | - |
|
||||||
|
| USE_I2S_AUDIO | - | - | - | - | - | - | - | - |
|
||||||
|
| USE_TTGO_WATCH | - | - | - | - | - | - | - | - |
|
||||||
|
@ -80,4 +80,4 @@ Module | LCode | Description
|
|||||||
74 Sonoff D1 | x | Sonoff D1 Wifi and RF Dimmer
|
74 Sonoff D1 | x | Sonoff D1 Wifi and RF Dimmer
|
||||||
75 Sonoff ZbBridge | x | Sonoff Zigbee bridge
|
75 Sonoff ZbBridge | x | Sonoff Zigbee bridge
|
||||||
|
|
||||||
Over 1400 additional devices are supported using [templates](TEMPLATES.md).
|
Over 1500 additional devices are supported using [templates](TEMPLATES.md).
|
||||||
|
@ -55,19 +55,27 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
|
|||||||
|
|
||||||
### Version 8.5.0 Hannah
|
### Version 8.5.0 Hannah
|
||||||
|
|
||||||
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
|
- Remove support for direct upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
|
||||||
- Change references from http://thehackbox.org to http://ota.tasmota.com
|
- Change references from http://thehackbox.org/tasmota/ to http://ota.tasmota.com/tasmota/
|
||||||
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
|
- Change triple-mode TLS via configuration in a single firmware (TLS AWS IoT, Letsencrypt and No-TLS)
|
||||||
|
- Change White blend mode to using command ``SetOption 105`` instead of ``RGBWWTable``
|
||||||
- Fix ESP32 PWM range
|
- Fix ESP32 PWM range
|
||||||
- Fix display power control (#9114)
|
- Fix display power control (#9114)
|
||||||
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add Zigbee better support for IKEA Motion Sensor
|
- Add command ``SetOption102 0/1`` to set Baud rate for Teleinfo communication (0 = 1200 or 1 = 9600)
|
||||||
|
- Add command ``SetOption103 0/1`` to set TLS mode when TLS is selected
|
||||||
|
- Add command ``SetOption104 1`` to disable all MQTT retained messages
|
||||||
|
- Add command ``SetOption106 1`` to create a virtual White ColorTemp for RGBW lights
|
||||||
|
- Add command ``SetOption107 0/1`` to select virtual White as (0) Warm or (1) Cold
|
||||||
|
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1)
|
||||||
- Add command ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
|
- Add command ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
|
||||||
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
|
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
|
||||||
- Add ESP32 Analog input support for GPIO32 to GPIO39
|
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
|
||||||
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
|
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
|
||||||
- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors
|
- Add Zigbee better support for IKEA Motion Sensor
|
||||||
|
- Add Zigbee web gui widget for Battery and Temp/Humidity/Pressure sensors
|
||||||
- Add Zigbee web ui for power metering plugs
|
- Add Zigbee web ui for power metering plugs
|
||||||
- Add better config corruption recovery (#9046)
|
- Add better configuration corruption recovery (#9046)
|
||||||
- Add virtual CT for 4 channels lights, emulating a 5th channel
|
- Add virtual CT for 4 channels lights, emulating a 5th channel
|
||||||
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
|
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
|
||||||
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
|
- Add ESP32 Analog input support for GPIO32 to GPIO39
|
||||||
|
- Add experimental support for ESP32 TTGO Watch and I2S Audio by Gerhard Mutz
|
||||||
|
452
TEMPLATES.md
452
TEMPLATES.md
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,9 @@ src_dir = tasmota
|
|||||||
build_dir = .pioenvs
|
build_dir = .pioenvs
|
||||||
workspace_dir = .pioenvs
|
workspace_dir = .pioenvs
|
||||||
build_cache_dir = .cache
|
build_cache_dir = .cache
|
||||||
extra_configs = platformio_tasmota_env.ini
|
extra_configs = platformio_tasmota32.ini
|
||||||
|
platformio_tasmota_env.ini
|
||||||
|
platformio_tasmota_env32.ini
|
||||||
platformio_override.ini
|
platformio_override.ini
|
||||||
|
|
||||||
; *** Build/upload environment
|
; *** Build/upload environment
|
||||||
@ -125,6 +127,6 @@ build_flags = -DUSE_IR_REMOTE_FULL
|
|||||||
[core]
|
[core]
|
||||||
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4
|
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4
|
||||||
platform = espressif8266@2.6.2
|
platform = espressif8266@2.6.2
|
||||||
platform_packages = framework-arduinoespressif8266 @ https://github.com/tasmota/Arduino/releases/download/2.7.4.1/esp8266-2.7.4.1.zip
|
platform_packages = jason2866/framework-arduinoespressif8266
|
||||||
build_unflags = ${esp_defaults.build_unflags}
|
build_unflags = ${esp_defaults.build_unflags}
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
|
@ -9,8 +9,7 @@
|
|||||||
; http://docs.platformio.org/en/stable/projectconf.html
|
; http://docs.platformio.org/en/stable/projectconf.html
|
||||||
|
|
||||||
[platformio]
|
[platformio]
|
||||||
extra_configs = platformio_tasmota_env32.ini
|
extra_configs = platformio_tasmota_cenv.ini
|
||||||
platformio_tasmota_cenv.ini
|
|
||||||
|
|
||||||
; *** Build/upload environment
|
; *** Build/upload environment
|
||||||
default_envs =
|
default_envs =
|
||||||
@ -89,7 +88,7 @@ extra_scripts = ${scripts_defaults.extra_scripts}
|
|||||||
[tasmota_stage]
|
[tasmota_stage]
|
||||||
; *** Esp8266 core for Arduino version Tasmota stage
|
; *** Esp8266 core for Arduino version Tasmota stage
|
||||||
platform = espressif8266@2.6.2
|
platform = espressif8266@2.6.2
|
||||||
platform_packages = framework-arduinoespressif8266 @ https://github.com/tasmota/Arduino/releases/download/2.7.4.1/esp8266-2.7.4.1.zip
|
platform_packages = jason2866/framework-arduinoespressif8266
|
||||||
build_unflags = ${esp_defaults.build_unflags}
|
build_unflags = ${esp_defaults.build_unflags}
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
|
|
||||||
@ -167,44 +166,3 @@ build_type = debug
|
|||||||
build_unflags = ${esp_defaults.build_unflags}
|
build_unflags = ${esp_defaults.build_unflags}
|
||||||
build_flags = ${esp82xx_defaults.build_flags}
|
build_flags = ${esp82xx_defaults.build_flags}
|
||||||
-Wstack-usage=300
|
-Wstack-usage=300
|
||||||
|
|
||||||
|
|
||||||
; *** Experimental ESP32 Tasmota version ***
|
|
||||||
; *** expect the unexpected. Many features not working!!! ***
|
|
||||||
|
|
||||||
[common32]
|
|
||||||
platform = espressif32@1.12.4
|
|
||||||
platform_packages = tool-esptoolpy@1.20800.0
|
|
||||||
board = esp32dev
|
|
||||||
board_build.ldscript = esp32_out.ld
|
|
||||||
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
|
||||||
board_build.flash_mode = ${common.board_build.flash_mode}
|
|
||||||
board_build.f_flash = ${common.board_build.f_flash}
|
|
||||||
board_build.f_cpu = ${common.board_build.f_cpu}
|
|
||||||
build_unflags = ${esp_defaults.build_unflags}
|
|
||||||
-Wpointer-arith
|
|
||||||
monitor_speed = ${common.monitor_speed}
|
|
||||||
upload_port = ${common.upload_port}
|
|
||||||
upload_resetmethod = ${common.upload_resetmethod}
|
|
||||||
upload_speed = 921600
|
|
||||||
extra_scripts = ${common.extra_scripts}
|
|
||||||
|
|
||||||
build_flags = ${esp_defaults.build_flags}
|
|
||||||
|
|
||||||
-D CORE_DEBUG_LEVEL=0
|
|
||||||
-D BUFFER_LENGTH=128
|
|
||||||
-D MQTT_MAX_PACKET_SIZE=1200
|
|
||||||
-D uint32=uint32_t
|
|
||||||
-D uint16=uint16_t
|
|
||||||
-D uint8=uint8_t
|
|
||||||
-D sint8_t=int8_t
|
|
||||||
-D sint32_t=int32_t
|
|
||||||
-D sint16_t=int16_t
|
|
||||||
-D memcpy_P=memcpy
|
|
||||||
-D memcmp_P=memcmp
|
|
||||||
|
|
||||||
lib_extra_dirs =
|
|
||||||
libesp32
|
|
||||||
|
|
||||||
lib_ignore =
|
|
||||||
cc1101
|
|
||||||
|
39
platformio_tasmota32.ini
Normal file
39
platformio_tasmota32.ini
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
; *** BETA ESP32 Tasmota version ***
|
||||||
|
; *** expect the unexpected. Some features not working!!! ***
|
||||||
|
|
||||||
|
[common32]
|
||||||
|
platform = espressif32@1.12.4
|
||||||
|
platform_packages = tool-esptoolpy@1.20800.0
|
||||||
|
board = esp32dev
|
||||||
|
board_build.ldscript = esp32_out.ld
|
||||||
|
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
|
||||||
|
board_build.flash_mode = ${common.board_build.flash_mode}
|
||||||
|
board_build.f_flash = ${common.board_build.f_flash}
|
||||||
|
board_build.f_cpu = ${common.board_build.f_cpu}
|
||||||
|
build_unflags = ${esp_defaults.build_unflags}
|
||||||
|
-Wpointer-arith
|
||||||
|
monitor_speed = ${common.monitor_speed}
|
||||||
|
upload_port = ${common.upload_port}
|
||||||
|
upload_resetmethod = ${common.upload_resetmethod}
|
||||||
|
upload_speed = 921600
|
||||||
|
extra_scripts = ${common.extra_scripts}
|
||||||
|
|
||||||
|
build_flags = ${esp_defaults.build_flags}
|
||||||
|
|
||||||
|
-D CORE_DEBUG_LEVEL=0
|
||||||
|
-D BUFFER_LENGTH=128
|
||||||
|
-D MQTT_MAX_PACKET_SIZE=1200
|
||||||
|
-D uint32=uint32_t
|
||||||
|
-D uint16=uint16_t
|
||||||
|
-D uint8=uint8_t
|
||||||
|
-D sint8_t=int8_t
|
||||||
|
-D sint32_t=int32_t
|
||||||
|
-D sint16_t=int16_t
|
||||||
|
-D memcpy_P=memcpy
|
||||||
|
-D memcmp_P=memcmp
|
||||||
|
|
||||||
|
lib_extra_dirs =
|
||||||
|
libesp32
|
||||||
|
|
||||||
|
lib_ignore =
|
||||||
|
cc1101
|
@ -8,15 +8,16 @@
|
|||||||
|
|
||||||
### 8.4.0.3 20200823
|
### 8.4.0.3 20200823
|
||||||
|
|
||||||
- Change references from http://thehackbox.org to http://ota.tasmota.com
|
- Change references from http://thehackbox.org/tasmota/ to http://ota.tasmota.com/tasmota/
|
||||||
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
|
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
|
||||||
- Add Zigbee web ui widget for Lights
|
- Add Zigbee web ui widget for Lights
|
||||||
- Add ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
|
- Add ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
|
||||||
- Add Zigbee web ui for power metering plugs
|
- Add Zigbee web ui for power metering plugs
|
||||||
|
- Add experimental support for ESP32 TTGO Watch and I2S Audio by Gerhard Mutz
|
||||||
|
|
||||||
### 8.4.0.2 20200813
|
### 8.4.0.2 20200813
|
||||||
|
|
||||||
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
|
- Remove support for direct upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
|
||||||
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
|
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
|
||||||
- Fix display power control (#9114)
|
- Fix display power control (#9114)
|
||||||
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add better config corruption recovery (#9046)
|
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add better config corruption recovery (#9046)
|
||||||
|
@ -1,440 +0,0 @@
|
|||||||
/*
|
|
||||||
esp8266_waveform - General purpose waveform generation and control,
|
|
||||||
supporting outputs on all pins in parallel.
|
|
||||||
|
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
|
||||||
Copyright (c) 2020 Dirk O. Kaar.
|
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
|
||||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
|
||||||
set to 1-shot mode and is always loaded with the time until the next edge
|
|
||||||
of any live waveforms.
|
|
||||||
|
|
||||||
Up to one waveform generator per pin supported.
|
|
||||||
|
|
||||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
|
||||||
timer. This allows for removing interrupt jitter and delay as the counter
|
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
|
||||||
contiguous and only take effect on the next waveform transition,
|
|
||||||
allowing for smooth transitions.
|
|
||||||
|
|
||||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
|
||||||
|
|
||||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
|
||||||
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
|
||||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
|
|
||||||
#include "core_esp8266_waveform.h"
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "ets_sys.h"
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
|
||||||
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
|
||||||
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
|
||||||
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
|
||||||
// Maximum servicing time for any single IRQ
|
|
||||||
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
|
||||||
// The latency between in-ISR rearming of the timer and the earliest firing
|
|
||||||
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
|
||||||
// The SDK and hardware take some time to actually get to our NMI code
|
|
||||||
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
|
||||||
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
|
||||||
|
|
||||||
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
|
||||||
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
|
||||||
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
|
||||||
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
|
||||||
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
|
||||||
|
|
||||||
// Waveform generator can create tones, PWM, and servos
|
|
||||||
typedef struct {
|
|
||||||
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
|
||||||
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
|
||||||
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
|
||||||
int32_t adjDutyCcys; // Temporary correction for next period
|
|
||||||
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
|
||||||
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
|
||||||
WaveformMode mode;
|
|
||||||
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
|
||||||
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
|
||||||
} Waveform;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
Waveform pins[17]; // State of all possible pins
|
|
||||||
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
|
||||||
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
|
||||||
|
|
||||||
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
|
||||||
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
|
||||||
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
|
||||||
|
|
||||||
uint32_t(*timer1CB)() = nullptr;
|
|
||||||
|
|
||||||
bool timer1Running = false;
|
|
||||||
|
|
||||||
uint32_t nextEventCcy;
|
|
||||||
} waveform;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt on/off control
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
|
||||||
|
|
||||||
// Non-speed critical bits
|
|
||||||
#pragma GCC optimize ("Os")
|
|
||||||
|
|
||||||
static void initTimer() {
|
|
||||||
timer1_disable();
|
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
|
||||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
|
||||||
waveform.timer1Running = true;
|
|
||||||
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
|
||||||
timer1_disable();
|
|
||||||
timer1_isr_init();
|
|
||||||
waveform.timer1Running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
// Set a callback. Pass in NULL to stop it
|
|
||||||
void setTimer1Callback(uint32_t (*fn)()) {
|
|
||||||
waveform.timer1CB = fn;
|
|
||||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
|
||||||
if (!waveform.timer1Running && fn) {
|
|
||||||
initTimer();
|
|
||||||
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
|
|
||||||
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
|
||||||
return startWaveformClockCycles(pin,
|
|
||||||
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
|
|
||||||
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
|
||||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
|
||||||
// first, then it will immediately begin.
|
|
||||||
int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
|
|
||||||
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
|
|
||||||
uint32_t periodCcys = highCcys + lowCcys;
|
|
||||||
if (periodCcys < MAXIRQTICKSCCYS) {
|
|
||||||
if (!highCcys) {
|
|
||||||
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
|
||||||
}
|
|
||||||
else if (!lowCcys) {
|
|
||||||
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sanity checks, including mixed signed/unsigned arithmetic safety
|
|
||||||
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
|
|
||||||
static_cast<int32_t>(periodCcys) <= 0 ||
|
|
||||||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Waveform& wave = waveform.pins[pin];
|
|
||||||
wave.dutyCcys = highCcys;
|
|
||||||
wave.adjDutyCcys = 0;
|
|
||||||
wave.periodCcys = periodCcys;
|
|
||||||
wave.autoPwm = autoPwm;
|
|
||||||
|
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
|
||||||
const uint32_t pinBit = 1UL << pin;
|
|
||||||
if (!(waveform.enabled & pinBit)) {
|
|
||||||
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
|
|
||||||
wave.nextPeriodCcy = phaseOffsetCcys;
|
|
||||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
|
|
||||||
wave.mode = WaveformMode::INIT;
|
|
||||||
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
|
|
||||||
if (!wave.dutyCcys) {
|
|
||||||
// If initially at zero duty cycle, force GPIO off
|
|
||||||
if (pin == 16) {
|
|
||||||
GP16O = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
GPOC = pinBit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
|
||||||
waveform.toSetBits = 1UL << pin;
|
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
|
||||||
if (!waveform.timer1Running) {
|
|
||||||
initTimer();
|
|
||||||
}
|
|
||||||
else if (T1V > IRQLATENCYCCYS) {
|
|
||||||
// Must not interfere if Timer is due shortly
|
|
||||||
timer1_write(IRQLATENCYCCYS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
|
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
|
||||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
|
|
||||||
if (runTimeCcys) {
|
|
||||||
wave.mode = WaveformMode::UPDATEEXPIRY;
|
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
|
||||||
waveform.toSetBits = 1UL << pin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
|
||||||
while (waveform.toSetBits) {
|
|
||||||
delay(0); // Wait for waveform to update
|
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stops a waveform on a pin
|
|
||||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
|
||||||
// Can't possibly need to stop anything if there is no timer active
|
|
||||||
if (!waveform.timer1Running) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
|
||||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
|
||||||
const uint32_t pinBit = 1UL << pin;
|
|
||||||
if (waveform.enabled & pinBit) {
|
|
||||||
waveform.toDisableBits = 1UL << pin;
|
|
||||||
std::atomic_thread_fence(std::memory_order_release);
|
|
||||||
// Must not interfere if Timer is due shortly
|
|
||||||
if (T1V > IRQLATENCYCCYS) {
|
|
||||||
timer1_write(IRQLATENCYCCYS);
|
|
||||||
}
|
|
||||||
while (waveform.toDisableBits) {
|
|
||||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
|
||||||
std::atomic_thread_fence(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!waveform.enabled && !waveform.timer1CB) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Speed critical bits
|
|
||||||
#pragma GCC optimize ("O2")
|
|
||||||
|
|
||||||
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
|
|
||||||
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
|
|
||||||
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
|
|
||||||
if (ISCPUFREQ160MHZ) {
|
|
||||||
return isCPU2X ? ccys : (ccys >> 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return isCPU2X ? (ccys << 1) : ccys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|
||||||
const uint32_t isrStartCcy = ESP.getCycleCount();
|
|
||||||
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
|
||||||
const bool isCPU2X = CPU2X & 1;
|
|
||||||
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
|
||||||
// Handle enable/disable requests from main app.
|
|
||||||
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
|
||||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
|
||||||
waveform.toDisableBits = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waveform.toSetBits) {
|
|
||||||
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
|
||||||
Waveform& wave = waveform.pins[toSetPin];
|
|
||||||
switch (wave.mode) {
|
|
||||||
case WaveformMode::INIT:
|
|
||||||
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
|
||||||
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
|
||||||
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wave.nextPeriodCcy = waveform.nextEventCcy;
|
|
||||||
}
|
|
||||||
if (!wave.expiryCcy) {
|
|
||||||
wave.mode = WaveformMode::INFINITE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fall through
|
|
||||||
case WaveformMode::UPDATEEXPIRY:
|
|
||||||
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
|
||||||
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
|
||||||
wave.mode = WaveformMode::EXPIRES;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
waveform.toSetBits = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit the loop if the next event, if any, is sufficiently distant.
|
|
||||||
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
|
||||||
uint32_t busyPins = waveform.enabled;
|
|
||||||
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
|
||||||
|
|
||||||
uint32_t now = ESP.getCycleCount();
|
|
||||||
uint32_t isrNextEventCcy = now;
|
|
||||||
while (busyPins) {
|
|
||||||
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
|
||||||
waveform.nextEventCcy = isrNextEventCcy;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
isrNextEventCcy = waveform.nextEventCcy;
|
|
||||||
uint32_t loopPins = busyPins;
|
|
||||||
while (loopPins) {
|
|
||||||
const int pin = __builtin_ffsl(loopPins) - 1;
|
|
||||||
const uint32_t pinBit = 1UL << pin;
|
|
||||||
loopPins ^= pinBit;
|
|
||||||
|
|
||||||
Waveform& wave = waveform.pins[pin];
|
|
||||||
|
|
||||||
if (clockDrift) {
|
|
||||||
wave.endDutyCcy += clockDrift;
|
|
||||||
wave.nextPeriodCcy += clockDrift;
|
|
||||||
wave.expiryCcy += clockDrift;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
|
||||||
if (WaveformMode::EXPIRES == wave.mode &&
|
|
||||||
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
|
||||||
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
|
||||||
// Disable any waveforms that are done
|
|
||||||
waveform.enabled ^= pinBit;
|
|
||||||
busyPins ^= pinBit;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const int32_t overshootCcys = now - waveNextEventCcy;
|
|
||||||
if (overshootCcys >= 0) {
|
|
||||||
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
|
||||||
if (waveform.states & pinBit) {
|
|
||||||
// active configuration and forward are 100% duty
|
|
||||||
if (wave.periodCcys == wave.dutyCcys) {
|
|
||||||
wave.nextPeriodCcy += periodCcys;
|
|
||||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (wave.autoPwm) {
|
|
||||||
wave.adjDutyCcys += overshootCcys;
|
|
||||||
}
|
|
||||||
waveform.states ^= pinBit;
|
|
||||||
if (16 == pin) {
|
|
||||||
GP16O = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
GPOC = pinBit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
waveNextEventCcy = wave.nextPeriodCcy;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
wave.nextPeriodCcy += periodCcys;
|
|
||||||
if (!wave.dutyCcys) {
|
|
||||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
|
||||||
if (dutyCcys <= wave.adjDutyCcys) {
|
|
||||||
dutyCcys >>= 1;
|
|
||||||
wave.adjDutyCcys -= dutyCcys;
|
|
||||||
}
|
|
||||||
else if (wave.adjDutyCcys) {
|
|
||||||
dutyCcys -= wave.adjDutyCcys;
|
|
||||||
wave.adjDutyCcys = 0;
|
|
||||||
}
|
|
||||||
wave.endDutyCcy = now + dutyCcys;
|
|
||||||
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
|
||||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
|
||||||
}
|
|
||||||
waveform.states |= pinBit;
|
|
||||||
if (16 == pin) {
|
|
||||||
GP16O = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
GPOS = pinBit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
waveNextEventCcy = wave.endDutyCcy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
|
||||||
waveNextEventCcy = wave.expiryCcy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
|
||||||
busyPins ^= pinBit;
|
|
||||||
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
|
||||||
waveform.nextEventCcy = waveNextEventCcy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
|
||||||
isrNextEventCcy = waveNextEventCcy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
now = ESP.getCycleCount();
|
|
||||||
}
|
|
||||||
clockDrift = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t callbackCcys = 0;
|
|
||||||
if (waveform.timer1CB) {
|
|
||||||
callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X);
|
|
||||||
}
|
|
||||||
now = ESP.getCycleCount();
|
|
||||||
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
|
||||||
// Account for unknown duration of timer1CB().
|
|
||||||
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
|
||||||
waveform.nextEventCcy = now + callbackCcys;
|
|
||||||
nextEventCcys = callbackCcys;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
|
||||||
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
|
||||||
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
|
||||||
if (isCPU2X) {
|
|
||||||
nextEventCcys >>= 1;
|
|
||||||
deltaIrqCcys >>= 1;
|
|
||||||
irqLatencyCcys >>= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Firing timer too soon, the NMI occurs before ISR has returned.
|
|
||||||
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
|
||||||
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
|
||||||
nextEventCcys = irqLatencyCcys;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nextEventCcys -= deltaIrqCcys;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register access is fast and edge IRQ was configured before.
|
|
||||||
T1L = nextEventCcys;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
esp8266_waveform - General purpose waveform generation and control,
|
|
||||||
supporting outputs on all pins in parallel.
|
|
||||||
|
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
|
||||||
Copyright (c) 2020 Dirk O. Kaar.
|
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
|
||||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
|
||||||
set to 1-shot mode and is always loaded with the time until the next edge
|
|
||||||
of any live waveforms.
|
|
||||||
|
|
||||||
Up to one waveform generator per pin supported.
|
|
||||||
|
|
||||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
|
||||||
timer. This allows for removing interrupt jitter and delay as the counter
|
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
|
||||||
contiguous and only take effect on the next waveform transition,
|
|
||||||
allowing for smooth transitions.
|
|
||||||
|
|
||||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
|
||||||
|
|
||||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
|
||||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
|
||||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#ifndef __ESP8266_WAVEFORM_H
|
|
||||||
#define __ESP8266_WAVEFORM_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Start or change a waveform of the specified high and low times on specific pin.
|
|
||||||
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
|
||||||
// full period.
|
|
||||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
|
||||||
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
|
||||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
|
||||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
|
||||||
// Returns true or false on success or failure.
|
|
||||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS,
|
|
||||||
uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
|
||||||
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
|
||||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
|
|
||||||
// full period.
|
|
||||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
|
||||||
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
|
|
||||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
|
||||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
|
||||||
// Returns true or false on success or failure.
|
|
||||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys,
|
|
||||||
uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
|
||||||
// Stop a waveform, if any, on the specified pin.
|
|
||||||
// Returns true or false on success or failure.
|
|
||||||
int stopWaveform(uint8_t pin);
|
|
||||||
|
|
||||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
|
||||||
// callback returns the number of microseconds until the next desired call.
|
|
||||||
// However, since it is called every timer1 interrupt, it may be called
|
|
||||||
// again before this period. It should therefore use the ESP Cycle Counter
|
|
||||||
// to determine whether or not to perform an operation.
|
|
||||||
// Pass in NULL to disable the callback and, if no other waveforms being
|
|
||||||
// generated, stop the timer as well.
|
|
||||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
|
||||||
void setTimer1Callback(uint32_t (*fn)());
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
@ -1,269 +0,0 @@
|
|||||||
/*
|
|
||||||
digital.c - wiring digital implementation for esp8266
|
|
||||||
|
|
||||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
|
|
||||||
#define ARDUINO_MAIN
|
|
||||||
#include "wiring_private.h"
|
|
||||||
#include "pins_arduino.h"
|
|
||||||
#include "c_types.h"
|
|
||||||
#include "eagle_soc.h"
|
|
||||||
#include "ets_sys.h"
|
|
||||||
#include "user_interface.h"
|
|
||||||
#include "core_esp8266_waveform.h"
|
|
||||||
#include "interrupts.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 };
|
|
||||||
|
|
||||||
extern void __pinMode(uint8_t pin, uint8_t mode) {
|
|
||||||
if(pin < 16){
|
|
||||||
if(mode == SPECIAL){
|
|
||||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
|
||||||
GPEC = (1 << pin); //Disable
|
|
||||||
GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin)
|
|
||||||
if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
|
|
||||||
} else if(mode & FUNCTION_0){
|
|
||||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
|
||||||
GPEC = (1 << pin); //Disable
|
|
||||||
GPF(pin) = GPFFS((mode >> 4) & 0x07);
|
|
||||||
if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
|
|
||||||
} else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){
|
|
||||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
|
||||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
|
||||||
if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD);
|
|
||||||
GPES = (1 << pin); //Enable
|
|
||||||
} else if(mode == INPUT || mode == INPUT_PULLUP){
|
|
||||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
|
||||||
GPEC = (1 << pin); //Disable
|
|
||||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
|
||||||
if(mode == INPUT_PULLUP) {
|
|
||||||
GPF(pin) |= (1 << GPFPU); // Enable Pullup
|
|
||||||
}
|
|
||||||
} else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){
|
|
||||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
|
||||||
GPEC = (1 << pin); //Disable
|
|
||||||
if(mode == WAKEUP_PULLUP) {
|
|
||||||
GPF(pin) |= (1 << GPFPU); // Enable Pullup
|
|
||||||
GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED)
|
|
||||||
} else {
|
|
||||||
GPF(pin) |= (1 << GPFPD); // Enable Pulldown
|
|
||||||
GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(pin == 16){
|
|
||||||
GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
|
||||||
GPC16 = 0;
|
|
||||||
if(mode == INPUT || mode == INPUT_PULLDOWN_16){
|
|
||||||
if(mode == INPUT_PULLDOWN_16){
|
|
||||||
GPF16 |= (1 << GP16FPD);//Enable Pulldown
|
|
||||||
}
|
|
||||||
GP16E &= ~1;
|
|
||||||
} else if(mode == OUTPUT){
|
|
||||||
GP16E |= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
|
||||||
stopWaveform(pin);
|
|
||||||
if(pin < 16){
|
|
||||||
if(val) GPOS = (1 << pin);
|
|
||||||
else GPOC = (1 << pin);
|
|
||||||
} else if(pin == 16){
|
|
||||||
if(val) GP16O |= 1;
|
|
||||||
else GP16O &= ~1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
|
|
||||||
if(pin < 16){
|
|
||||||
return GPIP(pin);
|
|
||||||
} else if(pin == 16){
|
|
||||||
return GP16I & 0x01;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
GPIO INTERRUPTS
|
|
||||||
*/
|
|
||||||
|
|
||||||
typedef void (*voidFuncPtr)(void);
|
|
||||||
typedef void (*voidFuncPtrArg)(void*);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
uint8_t mode;
|
|
||||||
voidFuncPtr fn;
|
|
||||||
void * arg;
|
|
||||||
bool functional;
|
|
||||||
} interrupt_handler_t;
|
|
||||||
|
|
||||||
//duplicate from functionalInterrupt.h keep in sync
|
|
||||||
typedef struct InterruptInfo {
|
|
||||||
uint8_t pin;
|
|
||||||
uint8_t value;
|
|
||||||
uint32_t micro;
|
|
||||||
} InterruptInfo;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
InterruptInfo* interruptInfo;
|
|
||||||
void* functionInfo;
|
|
||||||
} ArgStructure;
|
|
||||||
|
|
||||||
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
|
||||||
static uint32_t interrupt_reg = 0;
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
|
|
||||||
{
|
|
||||||
(void) arg;
|
|
||||||
(void) frame;
|
|
||||||
uint32_t status = GPIE;
|
|
||||||
GPIEC = status;//clear them interrupts
|
|
||||||
uint32_t levels = GPI;
|
|
||||||
if(status == 0 || interrupt_reg == 0) return;
|
|
||||||
ETS_GPIO_INTR_DISABLE();
|
|
||||||
int i = 0;
|
|
||||||
uint32_t changedbits = status & interrupt_reg;
|
|
||||||
while(changedbits){
|
|
||||||
while(!(changedbits & (1 << i))) i++;
|
|
||||||
changedbits &= ~(1 << i);
|
|
||||||
interrupt_handler_t *handler = &interrupt_handlers[i];
|
|
||||||
if (handler->fn &&
|
|
||||||
(handler->mode == CHANGE ||
|
|
||||||
(handler->mode & 1) == !!(levels & (1 << i)))) {
|
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
|
||||||
// we disable them before we call the client ISR
|
|
||||||
esp8266::InterruptLock irqLock; // stop other interrupts
|
|
||||||
if (handler->functional)
|
|
||||||
{
|
|
||||||
ArgStructure* localArg = (ArgStructure*)handler->arg;
|
|
||||||
if (localArg && localArg->interruptInfo)
|
|
||||||
{
|
|
||||||
localArg->interruptInfo->pin = i;
|
|
||||||
localArg->interruptInfo->value = __digitalRead(i);
|
|
||||||
localArg->interruptInfo->micro = micros();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (handler->arg)
|
|
||||||
{
|
|
||||||
((voidFuncPtrArg)handler->fn)(handler->arg);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
handler->fn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ETS_GPIO_INTR_ENABLE();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void cleanupFunctional(void* arg);
|
|
||||||
|
|
||||||
static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional)
|
|
||||||
{
|
|
||||||
interrupt_handler_t* handler = &interrupt_handlers[pin];
|
|
||||||
handler->mode = mode;
|
|
||||||
handler->fn = userFunc;
|
|
||||||
if (handler->functional && handler->arg) // Clean when new attach without detach
|
|
||||||
{
|
|
||||||
cleanupFunctional(handler->arg);
|
|
||||||
}
|
|
||||||
handler->arg = arg;
|
|
||||||
handler->functional = functional;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode, bool functional)
|
|
||||||
{
|
|
||||||
// #5780
|
|
||||||
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
|
|
||||||
if ((uint32_t)userFunc >= 0x40200000)
|
|
||||||
{
|
|
||||||
// ISR not in IRAM
|
|
||||||
::printf((PGM_P)F("ISR not in IRAM!\r\n"));
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pin < 16) {
|
|
||||||
ETS_GPIO_INTR_DISABLE();
|
|
||||||
set_interrupt_handlers(pin, (voidFuncPtr)userFunc, arg, mode, functional);
|
|
||||||
interrupt_reg |= (1 << pin);
|
|
||||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
|
||||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
|
||||||
GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode"
|
|
||||||
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
|
|
||||||
ETS_GPIO_INTR_ENABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode)
|
|
||||||
{
|
|
||||||
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
|
|
||||||
if (pin < 16)
|
|
||||||
{
|
|
||||||
ETS_GPIO_INTR_DISABLE();
|
|
||||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
|
||||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
|
||||||
interrupt_reg &= ~(1 << pin);
|
|
||||||
set_interrupt_handlers(pin, nullptr, nullptr, 0, false);
|
|
||||||
if (interrupt_reg)
|
|
||||||
{
|
|
||||||
ETS_GPIO_INTR_ENABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode)
|
|
||||||
{
|
|
||||||
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, 0, mode, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __resetPins() {
|
|
||||||
for (int i = 0; i <= 16; ++i) {
|
|
||||||
if (!isFlashInterfacePin(i))
|
|
||||||
pinMode(i, INPUT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void initPins() {
|
|
||||||
//Disable UART interrupts
|
|
||||||
system_set_os_print(0);
|
|
||||||
U0IE = 0;
|
|
||||||
U1IE = 0;
|
|
||||||
|
|
||||||
resetPins();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void resetPins() __attribute__ ((weak, alias("__resetPins")));
|
|
||||||
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
|
|
||||||
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
|
|
||||||
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"), nothrow));
|
|
||||||
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
|
|
||||||
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode) __attribute__((weak, alias("__attachInterruptArg")));
|
|
||||||
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
pwm.c - analogWrite implementation for esp8266
|
|
||||||
|
|
||||||
Use the shared TIMER1 utilities to generate PWM signals
|
|
||||||
|
|
||||||
Original Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
|
||||||
modify it under the terms of the GNU Lesser General Public
|
|
||||||
License as published by the Free Software Foundation; either
|
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This library is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
|
||||||
License along with this library; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "core_esp8266_waveform.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
static uint32_t analogMap = 0;
|
|
||||||
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
|
|
||||||
static uint16_t analogFreq = 1000;
|
|
||||||
|
|
||||||
extern void __analogWriteRange(uint32_t range) {
|
|
||||||
if ((range >= 15) && (range <= 65535)) {
|
|
||||||
analogScale = range;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWriteResolution(int res) {
|
|
||||||
if ((res >= 4) && (res <= 16)) {
|
|
||||||
analogScale = (1 << res) - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWriteFreq(uint32_t freq) {
|
|
||||||
if (freq < 40) {
|
|
||||||
analogFreq = 40;
|
|
||||||
} else if (freq > 60000) {
|
|
||||||
analogFreq = 60000;
|
|
||||||
} else {
|
|
||||||
analogFreq = freq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
extern void __analogWrite(uint8_t pin, int val) {
|
|
||||||
if (pin > 16) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
|
|
||||||
if (val < 0) {
|
|
||||||
val = 0;
|
|
||||||
} else if (val > analogScale) {
|
|
||||||
val = analogScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
|
|
||||||
// val: the duty cycle: between 0 (always off) and 255 (always on).
|
|
||||||
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
|
|
||||||
|
|
||||||
if (analogMap & 1UL << pin) {
|
|
||||||
analogMap &= ~(1 << pin);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
pinMode(pin, OUTPUT);
|
|
||||||
}
|
|
||||||
uint32_t high = (analogPeriod * val) / analogScale;
|
|
||||||
uint32_t low = analogPeriod - high;
|
|
||||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
|
||||||
int phaseReference = __builtin_ffs(analogMap) - 1;
|
|
||||||
if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
|
|
||||||
analogMap |= (1 << pin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
|
|
||||||
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
|
|
||||||
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
|
|
||||||
extern void analogWriteResolution(int res) __attribute__((weak, alias("__analogWriteResolution")));
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ESP8266
|
|
@ -809,7 +809,7 @@
|
|||||||
#define D_SCRIPT_UPLOAD_FILES "Upload Dateien"
|
#define D_SCRIPT_UPLOAD_FILES "Upload Dateien"
|
||||||
|
|
||||||
//xsns_67_as3935.ino
|
//xsns_67_as3935.ino
|
||||||
#define D_AS3935_GAIN "Rauschpegel:"
|
#define D_AS3935_GAIN "Umgebung:"
|
||||||
#define D_AS3935_ENERGY "Energie:"
|
#define D_AS3935_ENERGY "Energie:"
|
||||||
#define D_AS3935_DISTANCE "Entfernung:"
|
#define D_AS3935_DISTANCE "Entfernung:"
|
||||||
#define D_AS3935_DISTURBER "Entstörer:"
|
#define D_AS3935_DISTURBER "Entstörer:"
|
||||||
@ -823,7 +823,7 @@
|
|||||||
#define D_AS3935_NOISE "Rauschen entdeckt"
|
#define D_AS3935_NOISE "Rauschen entdeckt"
|
||||||
#define D_AS3935_DISTDET "Störer entdeckt"
|
#define D_AS3935_DISTDET "Störer entdeckt"
|
||||||
#define D_AS3935_INTNOEV "Interrupt ohne Grund!"
|
#define D_AS3935_INTNOEV "Interrupt ohne Grund!"
|
||||||
#define D_AS3935_FLICKER "IRQ Pin flackerd!"
|
#define D_AS3935_FLICKER "IRQ Pin flackert!"
|
||||||
#define D_AS3935_POWEROFF "Ausgeschaltet"
|
#define D_AS3935_POWEROFF "Ausgeschaltet"
|
||||||
#define D_AS3935_NOMESS "lausche..."
|
#define D_AS3935_NOMESS "lausche..."
|
||||||
#define D_AS3935_ON "On"
|
#define D_AS3935_ON "On"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
it-IT.h - localization for Italian - Italy for Tasmota
|
it-IT.h - localization for Italian - Italy for Tasmota
|
||||||
|
|
||||||
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 02.09.2020
|
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 05.09.2020
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -823,8 +823,8 @@
|
|||||||
#define D_AS3935_NOISE "rilevato rumore"
|
#define D_AS3935_NOISE "rilevato rumore"
|
||||||
#define D_AS3935_DISTDET "rilevato disturbatore"
|
#define D_AS3935_DISTDET "rilevato disturbatore"
|
||||||
#define D_AS3935_INTNOEV "Interrupt senza evento!"
|
#define D_AS3935_INTNOEV "Interrupt senza evento!"
|
||||||
#define D_AS3935_FLICKER "IRQ flicker!"
|
#define D_AS3935_FLICKER "Flicker PIN IRQ!"
|
||||||
#define D_AS3935_POWEROFF "Power Off"
|
#define D_AS3935_POWEROFF "Spegnimento"
|
||||||
#define D_AS3935_NOMESS "in ascolto..."
|
#define D_AS3935_NOMESS "in ascolto..."
|
||||||
#define D_AS3935_ON "ON"
|
#define D_AS3935_ON "ON"
|
||||||
#define D_AS3935_OFF "OFF"
|
#define D_AS3935_OFF "OFF"
|
||||||
|
@ -598,7 +598,9 @@ void GetFeatures(void)
|
|||||||
#ifdef USE_DYP
|
#ifdef USE_DYP
|
||||||
feature6 |= 0x00400000; // xsns_76_dyp.ino
|
feature6 |= 0x00400000; // xsns_76_dyp.ino
|
||||||
#endif
|
#endif
|
||||||
// feature6 |= 0x00800000;
|
#ifdef USE_I2S_AUDIO
|
||||||
|
feature6 |= 0x00800000; // xdrv_42_i2s_audio.ino
|
||||||
|
#endif
|
||||||
|
|
||||||
// feature6 |= 0x01000000;
|
// feature6 |= 0x01000000;
|
||||||
// feature6 |= 0x02000000;
|
// feature6 |= 0x02000000;
|
||||||
@ -606,7 +608,9 @@ void GetFeatures(void)
|
|||||||
// feature6 |= 0x08000000;
|
// feature6 |= 0x08000000;
|
||||||
|
|
||||||
// feature6 |= 0x10000000;
|
// feature6 |= 0x10000000;
|
||||||
// feature6 |= 0x20000000;
|
#if defined(ESP32) && defined(USE_TTGO_WATCH)
|
||||||
|
feature6 |= 0x20000000; // xdrv_83_esp32watch.ino
|
||||||
|
#endif
|
||||||
#if defined(ESP32) && defined(USE_ETHERNET)
|
#if defined(ESP32) && defined(USE_ETHERNET)
|
||||||
feature6 |= 0x40000000; // xdrv_82_ethernet.ino
|
feature6 |= 0x40000000; // xdrv_82_ethernet.ino
|
||||||
#endif
|
#endif
|
||||||
|
186
tasmota/support_light_list.ino
Normal file
186
tasmota/support_light_list.ino
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
|
||||||
|
|
||||||
|
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* private class for Linked List element
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
template <typename T>
|
||||||
|
class LList;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class LList_elt {
|
||||||
|
public:
|
||||||
|
|
||||||
|
LList_elt() : _next(nullptr), _val() {}
|
||||||
|
|
||||||
|
inline T & val(void) { return _val; }
|
||||||
|
inline LList_elt<T> * next(void) { return _next; }
|
||||||
|
inline void next(LList_elt<T> * next) { _next = next; }
|
||||||
|
|
||||||
|
friend class LList<T>;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LList_elt<T> * _next;
|
||||||
|
T _val;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* Lightweight Linked List - optimized for low code size
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
template <typename T>
|
||||||
|
class LList {
|
||||||
|
public:
|
||||||
|
LList() : _head(nullptr) {}
|
||||||
|
~LList() { reset(); }
|
||||||
|
|
||||||
|
// remove elements
|
||||||
|
void removeHead(void); // remove first element
|
||||||
|
void reset(void); // remove all elements
|
||||||
|
void remove(const T * val);
|
||||||
|
|
||||||
|
// read the list
|
||||||
|
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
|
||||||
|
size_t length(void) const;
|
||||||
|
inline T * head(void) { return _head ? &_head->_val : nullptr; }
|
||||||
|
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
|
||||||
|
const T * at(size_t index) const ;
|
||||||
|
// non-const variants
|
||||||
|
// not very academic cast but reduces code size
|
||||||
|
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
|
||||||
|
|
||||||
|
// adding elements
|
||||||
|
T & addHead(void);
|
||||||
|
T & addHead(const T &val);
|
||||||
|
T & addToLast(void);
|
||||||
|
|
||||||
|
// iterator
|
||||||
|
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
|
||||||
|
class iterator {
|
||||||
|
public:
|
||||||
|
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||||
|
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||||
|
bool operator!=(const iterator & other) const { return cur != other.cur; }
|
||||||
|
T & operator*() const { return cur->_val; }
|
||||||
|
private:
|
||||||
|
LList_elt<T> *cur;
|
||||||
|
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||||
|
};
|
||||||
|
iterator begin() { return iterator(this->_head); } // start with 'head'
|
||||||
|
iterator end() { return iterator(nullptr); } // end with null pointer
|
||||||
|
|
||||||
|
// const iterator
|
||||||
|
class const_iterator {
|
||||||
|
public:
|
||||||
|
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||||
|
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||||
|
bool operator!=(const_iterator & other) const { return cur != other.cur; }
|
||||||
|
const T & operator*() const { return cur->_val; }
|
||||||
|
private:
|
||||||
|
const LList_elt<T> *cur;
|
||||||
|
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||||
|
};
|
||||||
|
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
|
||||||
|
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LList_elt<T> * _head;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t LList<T>::length(void) const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (auto & elt : *this) {count++; }
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
const T * LList<T>::at(size_t index) const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto & elt : *this) {
|
||||||
|
if (index == count++) { return &elt; }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void LList<T>::reset(void) {
|
||||||
|
while (_head) {
|
||||||
|
LList_elt<T> * next = _head->next();
|
||||||
|
delete _head;
|
||||||
|
_head = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void LList<T>::removeHead(void) {
|
||||||
|
if (_head) {
|
||||||
|
LList_elt<T> * next = _head->next();
|
||||||
|
delete _head;
|
||||||
|
_head = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void LList<T>::remove(const T * val) {
|
||||||
|
if (nullptr == val) { return; }
|
||||||
|
// find element in chain and find pointer before
|
||||||
|
LList_elt<T> **curr_ptr = &_head;
|
||||||
|
while (*curr_ptr) {
|
||||||
|
LList_elt<T> * curr_elt = *curr_ptr;
|
||||||
|
if ( &(curr_elt->_val) == val) {
|
||||||
|
*curr_ptr = curr_elt->_next; // update previous pointer to next element
|
||||||
|
delete curr_elt;
|
||||||
|
break; // stop iteration now
|
||||||
|
}
|
||||||
|
curr_ptr = &((*curr_ptr)->_next); // move to next element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T & LList<T>::addHead(void) {
|
||||||
|
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||||
|
elt->next(_head); // insert at the head
|
||||||
|
_head = elt;
|
||||||
|
return elt->_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T & LList<T>::addHead(const T &val) {
|
||||||
|
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||||
|
elt->next(_head); // insert at the head
|
||||||
|
elt->_val = val;
|
||||||
|
_head = elt;
|
||||||
|
return elt->_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T & LList<T>::addToLast(void) {
|
||||||
|
LList_elt<T> **curr_ptr = &_head;
|
||||||
|
while (*curr_ptr) {
|
||||||
|
curr_ptr = &((*curr_ptr)->_next);
|
||||||
|
}
|
||||||
|
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||||
|
*curr_ptr = elt;
|
||||||
|
return elt->_val;
|
||||||
|
}
|
@ -237,3 +237,18 @@ public:
|
|||||||
_buf = nullptr;
|
_buf = nullptr;
|
||||||
}
|
}
|
||||||
} PreAllocatedSBuffer;
|
} PreAllocatedSBuffer;
|
||||||
|
|
||||||
|
// nullptr accepted
|
||||||
|
bool equalsSBuffer(const class SBuffer * buf1, const class SBuffer * buf2) {
|
||||||
|
if (buf1 == buf2) { return true; }
|
||||||
|
if (!buf1 && (buf2->len() == 0)) { return true; }
|
||||||
|
if (!buf2 && (buf1->len() == 0)) { return true; }
|
||||||
|
if (!buf1 || !buf2) { return false; } // at least one buf is not empty
|
||||||
|
// we know that both buf1 and buf2 are non-null
|
||||||
|
if (buf1->len() != buf2->len()) { return false; }
|
||||||
|
size_t len = buf1->len();
|
||||||
|
for (uint32_t i=0; i<len; i++) {
|
||||||
|
if (buf1->get8(i) != buf2->get8(i)) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -2712,6 +2712,7 @@ void HandleUploadLoop(void)
|
|||||||
|
|
||||||
HTTPUpload& upload = Webserver->upload();
|
HTTPUpload& upload = Webserver->upload();
|
||||||
|
|
||||||
|
// ***** Step1: Start upload file
|
||||||
if (UPLOAD_FILE_START == upload.status) {
|
if (UPLOAD_FILE_START == upload.status) {
|
||||||
restart_flag = 60;
|
restart_flag = 60;
|
||||||
if (0 == upload.filename.c_str()[0]) {
|
if (0 == upload.filename.c_str()[0]) {
|
||||||
@ -2752,7 +2753,10 @@ void HandleUploadLoop(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Web.upload_progress_dot_count = 0;
|
Web.upload_progress_dot_count = 0;
|
||||||
} else if (!Web.upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
|
}
|
||||||
|
|
||||||
|
// ***** Step2: Write upload file
|
||||||
|
else if (!Web.upload_error && (UPLOAD_FILE_WRITE == upload.status)) {
|
||||||
if (0 == upload.totalSize) {
|
if (0 == upload.totalSize) {
|
||||||
if (UPL_SETTINGS == Web.upload_file_type) {
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
||||||
Web.config_block_count = 0;
|
Web.config_block_count = 0;
|
||||||
@ -2803,6 +2807,7 @@ void HandleUploadLoop(void)
|
|||||||
// upload.buf[2] = 3; // Force DOUT - ESP8285
|
// upload.buf[2] = 3; // Force DOUT - ESP8285
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "File type %d"), Web.upload_file_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (UPL_SETTINGS == Web.upload_file_type) {
|
if (UPL_SETTINGS == Web.upload_file_type) {
|
||||||
@ -2874,7 +2879,10 @@ void HandleUploadLoop(void)
|
|||||||
if (!(Web.upload_progress_dot_count % 80)) { Serial.println(); }
|
if (!(Web.upload_progress_dot_count % 80)) { Serial.println(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if(!Web.upload_error && (UPLOAD_FILE_END == upload.status)) {
|
}
|
||||||
|
|
||||||
|
// ***** Step3: Finish upload file
|
||||||
|
else if(!Web.upload_error && (UPLOAD_FILE_END == upload.status)) {
|
||||||
if (_serialoutput && (Web.upload_progress_dot_count % 80)) {
|
if (_serialoutput && (Web.upload_progress_dot_count % 80)) {
|
||||||
Serial.println();
|
Serial.println();
|
||||||
}
|
}
|
||||||
@ -2950,9 +2958,12 @@ void HandleUploadLoop(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!Web.upload_error) {
|
if (!Web.upload_error) {
|
||||||
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
|
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes"), upload.totalSize);
|
||||||
}
|
}
|
||||||
} else if (UPLOAD_FILE_ABORTED == upload.status) {
|
}
|
||||||
|
|
||||||
|
// ***** Step4: Abort upload file
|
||||||
|
else if (UPLOAD_FILE_ABORTED == upload.status) {
|
||||||
restart_flag = 0;
|
restart_flag = 0;
|
||||||
MqttRetryCounter(0);
|
MqttRetryCounter(0);
|
||||||
#ifdef USE_COUNTER
|
#ifdef USE_COUNTER
|
||||||
|
752
tasmota/xdrv_23_zigbee_1z_libs.ino
Normal file
752
tasmota/xdrv_23_zigbee_1z_libs.ino
Normal file
@ -0,0 +1,752 @@
|
|||||||
|
/*
|
||||||
|
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
|
||||||
|
|
||||||
|
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Replacement libs for JSON to output a list of attributes
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
// simplified version of strcmp accepting both arguments to be in PMEM, and accepting nullptr arguments
|
||||||
|
// inspired from https://code.woboq.org/userspace/glibc/string/strcmp.c.html
|
||||||
|
int strcmp_PP(const char *p1, const char *p2) {
|
||||||
|
if (p1 == p2) { return 0; } // equality
|
||||||
|
if (!p1) { return -1; } // first string is null
|
||||||
|
if (!p2) { return 1; } // second string is null
|
||||||
|
const unsigned char *s1 = (const unsigned char *) p1;
|
||||||
|
const unsigned char *s2 = (const unsigned char *) p2;
|
||||||
|
unsigned char c1, c2;
|
||||||
|
do {
|
||||||
|
c1 = (unsigned char) pgm_read_byte(s1);
|
||||||
|
s1++;
|
||||||
|
c2 = (unsigned char) pgm_read_byte(s2);
|
||||||
|
s2++;
|
||||||
|
if (c1 == '\0')
|
||||||
|
return c1 - c2;
|
||||||
|
}
|
||||||
|
while (c1 == c2);
|
||||||
|
return c1 - c2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* Variables for Rules from last Zigbee message received
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
typedef struct Z_LastMessageVars {
|
||||||
|
uint16_t device; // device short address
|
||||||
|
uint16_t groupaddr; // group address
|
||||||
|
uint16_t cluster; // cluster id
|
||||||
|
uint8_t endpoint; // source endpoint
|
||||||
|
} Z_LastMessageVars;
|
||||||
|
|
||||||
|
Z_LastMessageVars gZbLastMessage;
|
||||||
|
|
||||||
|
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
|
||||||
|
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
|
||||||
|
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
|
||||||
|
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* Class for single attribute
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
enum class Za_type : uint8_t {
|
||||||
|
Za_none, // empty, translates into null in JSON
|
||||||
|
// numericals
|
||||||
|
Za_bool, // boolean, translates to true/false, uses uval32 to store
|
||||||
|
Za_uint, // unsigned 32 int, uses uval32
|
||||||
|
Za_int, // signed 32 int, uses ival32
|
||||||
|
Za_float, // float 32, uses fval
|
||||||
|
// non-nummericals
|
||||||
|
Za_raw, // bytes buffer, uses bval
|
||||||
|
Za_str // string, uses sval
|
||||||
|
};
|
||||||
|
|
||||||
|
class Z_attribute {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// attribute key, either cluster+attribute_id or plain name
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint16_t cluster;
|
||||||
|
uint16_t attr_id;
|
||||||
|
} id;
|
||||||
|
char * key;
|
||||||
|
} key;
|
||||||
|
// attribute value
|
||||||
|
union {
|
||||||
|
uint32_t uval32;
|
||||||
|
int32_t ival32;
|
||||||
|
float fval;
|
||||||
|
SBuffer* bval;
|
||||||
|
char* sval;
|
||||||
|
} val;
|
||||||
|
Za_type type; // uint8_t in size, type of attribute, see above
|
||||||
|
bool key_is_str; // is the key a string?
|
||||||
|
bool key_is_pmem; // is the string in progmem, so we don't need to make a copy
|
||||||
|
bool val_str_raw; // if val is String, it is raw JSON and should not be escaped
|
||||||
|
uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1)
|
||||||
|
|
||||||
|
// Constructor with all defaults
|
||||||
|
Z_attribute():
|
||||||
|
key{ .id = { 0x0000, 0x0000 } },
|
||||||
|
val{ .uval32 = 0x0000 },
|
||||||
|
type(Za_type::Za_none),
|
||||||
|
key_is_str(false),
|
||||||
|
key_is_pmem(false),
|
||||||
|
val_str_raw(false),
|
||||||
|
key_suffix(1)
|
||||||
|
{};
|
||||||
|
|
||||||
|
Z_attribute(const Z_attribute & rhs) {
|
||||||
|
deepCopy(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Z_attribute & operator = (const Z_attribute & rhs) {
|
||||||
|
freeKey();
|
||||||
|
freeVal();
|
||||||
|
deepCopy(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructor, free memory that was allocated
|
||||||
|
~Z_attribute() {
|
||||||
|
freeKey();
|
||||||
|
freeVal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// free any allocated memoruy for values
|
||||||
|
void freeVal(void) {
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_raw:
|
||||||
|
if (val.bval) { delete val.bval; val.bval = nullptr; }
|
||||||
|
break;
|
||||||
|
case Za_type::Za_str:
|
||||||
|
if (val.sval) { delete[] val.sval; val.sval = nullptr; }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// free any allocated memoruy for keys
|
||||||
|
void freeKey(void) {
|
||||||
|
if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
|
||||||
|
key.key = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set key name
|
||||||
|
void setKeyName(const char * _key, bool pmem = false) {
|
||||||
|
freeKey();
|
||||||
|
key_is_str = true;
|
||||||
|
key_is_pmem = pmem;
|
||||||
|
if (pmem) {
|
||||||
|
key.key = (char*) _key;
|
||||||
|
} else {
|
||||||
|
setKeyName(_key, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// provide two entries and concat
|
||||||
|
void setKeyName(const char * _key, const char * _key2) {
|
||||||
|
freeKey();
|
||||||
|
key_is_str = true;
|
||||||
|
key_is_pmem = false;
|
||||||
|
if (_key) {
|
||||||
|
size_t key_len = strlen_P(_key);
|
||||||
|
if (_key2) {
|
||||||
|
key_len += strlen_P(_key2);
|
||||||
|
}
|
||||||
|
key.key = new char[key_len+1];
|
||||||
|
strcpy_P(key.key, _key);
|
||||||
|
if (_key2) {
|
||||||
|
strcat_P(key.key, _key2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setKeyId(uint16_t cluster, uint16_t attr_id) {
|
||||||
|
freeKey();
|
||||||
|
key_is_str = false;
|
||||||
|
key.id.cluster = cluster;
|
||||||
|
key.id.attr_id = attr_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
void setNone(void) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val.uval32 = 0;
|
||||||
|
type = Za_type::Za_none;
|
||||||
|
}
|
||||||
|
void setUInt(uint32_t _val) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val.uval32 = _val;
|
||||||
|
type = Za_type::Za_uint;
|
||||||
|
}
|
||||||
|
void setBool(bool _val) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val.uval32 = _val;
|
||||||
|
type = Za_type::Za_bool;
|
||||||
|
}
|
||||||
|
void setInt(int32_t _val) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val.ival32 = _val;
|
||||||
|
type = Za_type::Za_int;
|
||||||
|
}
|
||||||
|
void setFloat(float _val) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val.fval = _val;
|
||||||
|
type = Za_type::Za_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBuf(const SBuffer &buf, size_t index, size_t len) {
|
||||||
|
freeVal();
|
||||||
|
if (len) {
|
||||||
|
val.bval = new SBuffer(len);
|
||||||
|
val.bval->addBuffer(buf.buf(index), len);
|
||||||
|
}
|
||||||
|
type = Za_type::Za_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the string value
|
||||||
|
// PMEM argument is allowed
|
||||||
|
// string will be copied, so it can be changed later
|
||||||
|
// nullptr is allowed and considered as empty string
|
||||||
|
// Note: memory is allocated only if string is non-empty
|
||||||
|
void setStr(const char * _val) {
|
||||||
|
freeVal(); // free any previously allocated memory
|
||||||
|
val_str_raw = false;
|
||||||
|
// val.sval is always nullptr after freeVal()
|
||||||
|
if (_val) {
|
||||||
|
size_t len = strlen_P(_val);
|
||||||
|
if (len) {
|
||||||
|
val.sval = new char[len+1];
|
||||||
|
strcpy_P(val.sval, _val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type = Za_type::Za_str;
|
||||||
|
}
|
||||||
|
inline void setStrRaw(const char * _val) {
|
||||||
|
setStr(_val);
|
||||||
|
val_str_raw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
|
||||||
|
// get num values
|
||||||
|
float getFloat(void) const {
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_bool:
|
||||||
|
case Za_type::Za_uint: return (float) val.uval32;
|
||||||
|
case Za_type::Za_int: return (float) val.ival32;
|
||||||
|
case Za_type::Za_float: return val.fval;
|
||||||
|
default: return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t getInt(void) const {
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_bool:
|
||||||
|
case Za_type::Za_uint: return (int32_t) val.uval32;
|
||||||
|
case Za_type::Za_int: return val.ival32;
|
||||||
|
case Za_type::Za_float: return (int32_t) val.fval;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getUInt(void) const {
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_bool:
|
||||||
|
case Za_type::Za_uint: return val.uval32;
|
||||||
|
case Za_type::Za_int: return (uint32_t) val.ival32;
|
||||||
|
case Za_type::Za_float: return (uint32_t) val.fval;
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getBool(void) const {
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_bool:
|
||||||
|
case Za_type::Za_uint: return val.uval32 ? true : false;
|
||||||
|
case Za_type::Za_int: return val.ival32 ? true : false;
|
||||||
|
case Za_type::Za_float: return val.fval ? true : false;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SBuffer * getRaw(void) const {
|
||||||
|
if (Za_type::Za_raw == type) { return val.bval; }
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always return a point to a string, if not defined then empty string.
|
||||||
|
// Never returns nullptr
|
||||||
|
const char * getStr(void) const {
|
||||||
|
if (Za_type::Za_str == type) { return val.sval; }
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const {
|
||||||
|
// check if keys are equal
|
||||||
|
if (key_is_str != attr2.key_is_str) { return false; }
|
||||||
|
if (key_is_str) {
|
||||||
|
if (strcmp_PP(key.key, attr2.key.key)) { return false; }
|
||||||
|
} else {
|
||||||
|
if ((key.id.cluster != attr2.key.id.cluster) ||
|
||||||
|
(key.id.attr_id != attr2.key.id.attr_id)) { return false; }
|
||||||
|
}
|
||||||
|
if (!ignore_key_suffix) {
|
||||||
|
if (key_suffix != attr2.key_suffix) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const {
|
||||||
|
if (!key_is_str) {
|
||||||
|
if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
|
||||||
|
if (suffix) {
|
||||||
|
if (key_suffix == suffix) { return true; }
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equalsKey(const char * name, uint8_t suffix = 0) const {
|
||||||
|
if (key_is_str) {
|
||||||
|
if (0 == strcmp_PP(key.key, name)) {
|
||||||
|
if (suffix) {
|
||||||
|
if (key_suffix == suffix) { return true; }
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equalsVal(const Z_attribute & attr2) const {
|
||||||
|
if (type != attr2.type) { return false; }
|
||||||
|
if ((type >= Za_type::Za_bool) && (type <= Za_type::Za_float)) {
|
||||||
|
// numerical value
|
||||||
|
if (val.uval32 != attr2.val.uval32) { return false; }
|
||||||
|
} else if (type == Za_type::Za_raw) {
|
||||||
|
// compare 2 Static buffers
|
||||||
|
return equalsSBuffer(val.bval, attr2.val.bval);
|
||||||
|
} else if (type == Za_type::Za_str) {
|
||||||
|
// if (val_str_raw != attr2.val_str_raw) { return false; }
|
||||||
|
if (strcmp_PP(val.sval, attr2.val.sval)) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool equals(const Z_attribute & attr2) const {
|
||||||
|
return equalsKey(attr2) && equalsVal(attr2);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toString(bool prefix_comma = false) const {
|
||||||
|
String res("");
|
||||||
|
if (prefix_comma) { res += ','; }
|
||||||
|
res += '"';
|
||||||
|
// compute the attribute name
|
||||||
|
if (key_is_str) {
|
||||||
|
if (key.key) { res += EscapeJSONString(key.key); }
|
||||||
|
else { res += F("null"); } // shouldn't happen
|
||||||
|
if (key_suffix > 1) {
|
||||||
|
res += key_suffix;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char attr_name[12];
|
||||||
|
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
|
||||||
|
res += attr_name;
|
||||||
|
if (key_suffix > 1) {
|
||||||
|
res += '+';
|
||||||
|
res += key_suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += F("\":");
|
||||||
|
// value part
|
||||||
|
switch (type) {
|
||||||
|
case Za_type::Za_none:
|
||||||
|
res += "null";
|
||||||
|
break;
|
||||||
|
case Za_type::Za_bool:
|
||||||
|
res += val.uval32 ? F("true") : F("false");
|
||||||
|
break;
|
||||||
|
case Za_type::Za_uint:
|
||||||
|
res += val.uval32;
|
||||||
|
break;
|
||||||
|
case Za_type::Za_int:
|
||||||
|
res += val.ival32;
|
||||||
|
break;
|
||||||
|
case Za_type::Za_float:
|
||||||
|
{
|
||||||
|
String fstr(val.fval, 2);
|
||||||
|
size_t last = fstr.length() - 1;
|
||||||
|
// remove trailing zeros
|
||||||
|
while (fstr[last] == '0') {
|
||||||
|
fstr.remove(last--);
|
||||||
|
}
|
||||||
|
// remove trailing dot
|
||||||
|
if (fstr[last] == '.') {
|
||||||
|
fstr.remove(last);
|
||||||
|
}
|
||||||
|
res += fstr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Za_type::Za_raw:
|
||||||
|
res += '"';
|
||||||
|
if (val.bval) {
|
||||||
|
size_t blen = val.bval->len();
|
||||||
|
// print as HEX
|
||||||
|
char hex[2*blen+1];
|
||||||
|
ToHex_P(val.bval->getBuffer(), blen, hex, sizeof(hex));
|
||||||
|
res += hex;
|
||||||
|
}
|
||||||
|
res += '"';
|
||||||
|
break;
|
||||||
|
case Za_type::Za_str:
|
||||||
|
if (val_str_raw) {
|
||||||
|
if (val.sval) { res += val.sval; }
|
||||||
|
} else {
|
||||||
|
res += '"';
|
||||||
|
if (val.sval) {
|
||||||
|
res += EscapeJSONString(val.sval); // escape JSON chars
|
||||||
|
}
|
||||||
|
res += '"';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy value from one attribute to another, without changing its type
|
||||||
|
void copyVal(const Z_attribute & rhs) {
|
||||||
|
freeVal();
|
||||||
|
// copy value
|
||||||
|
val.uval32 = 0x00000000;
|
||||||
|
type = rhs.type;
|
||||||
|
if (rhs.isNum()) {
|
||||||
|
val.uval32 = rhs.val.uval32;
|
||||||
|
} else if (rhs.type == Za_type::Za_raw) {
|
||||||
|
if (rhs.val.bval) {
|
||||||
|
val.bval = new SBuffer(rhs.val.bval->len());
|
||||||
|
val.bval->addBuffer(*(rhs.val.bval));
|
||||||
|
}
|
||||||
|
} else if (rhs.type == Za_type::Za_str) {
|
||||||
|
if (rhs.val.sval) {
|
||||||
|
size_t s_len = strlen_P(rhs.val.sval);
|
||||||
|
val.sval = new char[s_len+1];
|
||||||
|
strcpy_P(val.sval, rhs.val.sval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val_str_raw = rhs.val_str_raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void deepCopy(const Z_attribute & rhs) {
|
||||||
|
// copy key
|
||||||
|
if (!rhs.key_is_str) {
|
||||||
|
key.id.cluster = rhs.key.id.cluster;
|
||||||
|
key.id.attr_id = rhs.key.id.attr_id;
|
||||||
|
} else {
|
||||||
|
if (rhs.key_is_pmem) {
|
||||||
|
key.key = rhs.key.key; // PMEM, don't copy
|
||||||
|
} else {
|
||||||
|
key.key = nullptr;
|
||||||
|
if (rhs.key.key) {
|
||||||
|
size_t key_len = strlen_P(rhs.key.key);
|
||||||
|
if (key_len) {
|
||||||
|
key.key = new char[key_len+1];
|
||||||
|
strcpy_P(key.key, rhs.key.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_is_str = rhs.key_is_str;
|
||||||
|
key_is_pmem = rhs.key_is_pmem;
|
||||||
|
key_suffix = rhs.key_suffix;
|
||||||
|
// copy value
|
||||||
|
copyVal(rhs);
|
||||||
|
// don't touch next pointer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* Class for attribute array of values
|
||||||
|
* This is a helper function to generate a clean list of unsigned ints
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
class Z_json_array {
|
||||||
|
public:
|
||||||
|
|
||||||
|
Z_json_array(): val("[]") {} // start with empty array
|
||||||
|
void add(uint32_t uval32) {
|
||||||
|
// remove trailing ']'
|
||||||
|
val.remove(val.length()-1);
|
||||||
|
if (val.length() > 1) { // if not empty, prefix with comma
|
||||||
|
val += ',';
|
||||||
|
}
|
||||||
|
val += uval32;
|
||||||
|
val += ']';
|
||||||
|
}
|
||||||
|
String &toString(void) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private :
|
||||||
|
String val;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
*
|
||||||
|
* Class for attribute ordered list
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
// Attribute list
|
||||||
|
// Contains meta-information:
|
||||||
|
// - source endpoint (is conflicting)
|
||||||
|
// - LQI (if not conflicting)
|
||||||
|
class Z_attribute_list : public LList<Z_attribute> {
|
||||||
|
public:
|
||||||
|
uint8_t src_ep; // source endpoint, 0xFF if unknown
|
||||||
|
uint8_t lqi; // linkquality, 0xFF if unknown
|
||||||
|
uint16_t group_id; // group address OxFFFF if inknown
|
||||||
|
|
||||||
|
Z_attribute_list():
|
||||||
|
LList<Z_attribute>(), // call superclass constructor
|
||||||
|
src_ep(0xFF),
|
||||||
|
lqi(0xFF),
|
||||||
|
group_id(0xFFFF)
|
||||||
|
{};
|
||||||
|
|
||||||
|
// we don't define any destructor, the superclass destructor is automatically called
|
||||||
|
|
||||||
|
// reset object to its initial state
|
||||||
|
// free all allocated memory
|
||||||
|
void reset(void) {
|
||||||
|
LList<Z_attribute>::reset();
|
||||||
|
src_ep = 0xFF;
|
||||||
|
lqi = 0xFF;
|
||||||
|
group_id = 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; }
|
||||||
|
inline bool isValidLQI(void) const { return 0xFF != lqi; }
|
||||||
|
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; }
|
||||||
|
|
||||||
|
// the following addAttribute() compute the suffix and increments it
|
||||||
|
// Add attribute to the list, given cluster and attribute id
|
||||||
|
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||||
|
|
||||||
|
// Add attribute to the list, given name
|
||||||
|
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
|
||||||
|
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
|
||||||
|
inline Z_attribute & addAttribute(const __FlashStringHelper * name, uint8_t suffix = 0) {
|
||||||
|
return addAttribute((const char*) name, true, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from list by reference, if null or not found, then do nothing
|
||||||
|
inline void removeAttribute(const Z_attribute * attr) { remove(attr); }
|
||||||
|
|
||||||
|
// dump the entire structure as JSON, starting from head (as parameter)
|
||||||
|
// does not start not end with a comma
|
||||||
|
// do we enclosed in brackets '{' '}'
|
||||||
|
String toString(bool enclose_brackets = false) const;
|
||||||
|
|
||||||
|
// find if attribute with same key already exists, return null if not found
|
||||||
|
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
|
||||||
|
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
|
||||||
|
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis always count here
|
||||||
|
// non-const variants
|
||||||
|
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
|
||||||
|
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
|
||||||
|
}
|
||||||
|
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
|
||||||
|
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
|
||||||
|
}
|
||||||
|
inline Z_attribute * findAttribute(const Z_attribute &attr) {
|
||||||
|
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// count matching attributes, ignoring suffix
|
||||||
|
size_t countAttribute(uint16_t cluster, uint16_t attr_id) const ;
|
||||||
|
size_t countAttribute(const char * name) const ;
|
||||||
|
|
||||||
|
// if suffix == 0, we don't care and find the first match
|
||||||
|
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||||
|
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
|
||||||
|
// always care about suffix
|
||||||
|
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
|
||||||
|
// replace attribute with new value, suffix does care
|
||||||
|
Z_attribute & replaceOrCreate(const Z_attribute &attr);
|
||||||
|
|
||||||
|
// merge with secondary list, return true if ok, false if conflict
|
||||||
|
bool mergeList(const Z_attribute_list &list2);
|
||||||
|
};
|
||||||
|
|
||||||
|
// add a cluster/attr_id attribute at the end of the list
|
||||||
|
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||||
|
Z_attribute & attr = addToLast();
|
||||||
|
attr.key.id.cluster = cluster;
|
||||||
|
attr.key.id.attr_id = attr_id;
|
||||||
|
attr.key_is_str = false;
|
||||||
|
if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.attr_id); }
|
||||||
|
else { attr.key_suffix = suffix; }
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add a cluster/attr_id attribute at the end of the list
|
||||||
|
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
|
||||||
|
Z_attribute & attr = addToLast();
|
||||||
|
attr.setKeyName(name, pmem);
|
||||||
|
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||||
|
else { attr.key_suffix = suffix; }
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
|
||||||
|
Z_attribute & attr = addToLast();
|
||||||
|
attr.setKeyName(name, name2);
|
||||||
|
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||||
|
else { attr.key_suffix = suffix; }
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Z_attribute_list::toString(bool enclose_brackets) const {
|
||||||
|
String res = "";
|
||||||
|
if (enclose_brackets) { res += '{'; }
|
||||||
|
bool prefix_comma = false;
|
||||||
|
for (const auto & attr : *this) {
|
||||||
|
res += attr.toString(prefix_comma);
|
||||||
|
prefix_comma = true;
|
||||||
|
}
|
||||||
|
// add source endpoint
|
||||||
|
if (0xFF != src_ep) {
|
||||||
|
if (prefix_comma) { res += ','; }
|
||||||
|
prefix_comma = true;
|
||||||
|
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
|
||||||
|
res += src_ep;
|
||||||
|
}
|
||||||
|
// add group address
|
||||||
|
if (0xFFFF != group_id) {
|
||||||
|
if (prefix_comma) { res += ','; }
|
||||||
|
prefix_comma = true;
|
||||||
|
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
|
||||||
|
res += group_id;
|
||||||
|
}
|
||||||
|
// add lqi
|
||||||
|
if (0xFF != lqi) {
|
||||||
|
if (prefix_comma) { res += ','; }
|
||||||
|
prefix_comma = true;
|
||||||
|
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");
|
||||||
|
res += lqi;
|
||||||
|
}
|
||||||
|
if (enclose_brackets) { res += '}'; }
|
||||||
|
// done
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// suffis always count here
|
||||||
|
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
|
||||||
|
uint8_t suffix = attr.key_suffix;
|
||||||
|
if (attr.key_is_str) {
|
||||||
|
return findAttribute(attr.key.key, suffix);
|
||||||
|
} else {
|
||||||
|
return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
|
||||||
|
for (const auto & attr : *this) {
|
||||||
|
if (attr.equalsKey(cluster, attr_id, suffix)) { return &attr; }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto & attr : *this) {
|
||||||
|
if (attr.equalsKey(cluster, attr_id, 0)) { count++; }
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the existing attribute or create a new one
|
||||||
|
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||||
|
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
|
||||||
|
return found ? *found : addAttribute(cluster, attr_id, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
|
||||||
|
for (const auto & attr : *this) {
|
||||||
|
if (attr.equalsKey(name, suffix)) { return &attr; }
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
size_t Z_attribute_list::countAttribute(const char * name) const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto & attr : *this) {
|
||||||
|
if (attr.equalsKey(name, 0)) { count++; }
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
// return the existing attribute or create a new one
|
||||||
|
Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t suffix) {
|
||||||
|
Z_attribute * found = findAttribute(name, suffix);
|
||||||
|
return found ? *found : addAttribute(name, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// same but passing a Z_attribute as key
|
||||||
|
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
|
||||||
|
if (attr.key_is_str) {
|
||||||
|
return findOrCreateAttribute(attr.key.key, attr.key_suffix);
|
||||||
|
} else {
|
||||||
|
return findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// replace the entire content with new attribute or create
|
||||||
|
Z_attribute & Z_attribute_list::replaceOrCreate(const Z_attribute &attr) {
|
||||||
|
Z_attribute &new_attr = findOrCreateAttribute(attr);
|
||||||
|
new_attr.copyVal(attr);
|
||||||
|
return new_attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool Z_attribute_list::mergeList(const Z_attribute_list &attr_list) {
|
||||||
|
// Check source endpoint
|
||||||
|
if (0xFF == src_ep) {
|
||||||
|
src_ep = attr_list.src_ep;
|
||||||
|
} else if (0xFF != attr_list.src_ep) {
|
||||||
|
if (src_ep != attr_list.src_ep) { return false; }
|
||||||
|
}
|
||||||
|
if (0xFF != attr_list.lqi) {
|
||||||
|
lqi = attr_list.lqi;
|
||||||
|
}
|
||||||
|
for (auto & attr : attr_list) {
|
||||||
|
replaceOrCreate(attr);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_ZIGBEE
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#ifdef USE_ZIGBEE
|
#ifdef USE_ZIGBEE
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
|
||||||
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
|
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
|
||||||
#endif
|
#endif
|
||||||
@ -29,25 +27,6 @@ const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait f
|
|||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Structures for Rules variables related to the last received message
|
* Structures for Rules variables related to the last received message
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
typedef struct Z_LastMessageVars {
|
|
||||||
uint16_t device; // device short address
|
|
||||||
uint16_t groupaddr; // group address
|
|
||||||
uint16_t cluster; // cluster id
|
|
||||||
uint8_t endpoint; // source endpoint
|
|
||||||
} Z_LastMessageVars;
|
|
||||||
|
|
||||||
Z_LastMessageVars gZbLastMessage;
|
|
||||||
|
|
||||||
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
|
|
||||||
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
|
|
||||||
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
|
|
||||||
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
|
|
||||||
|
|
||||||
/*********************************************************************************************\
|
|
||||||
* Structures for device configuration
|
|
||||||
\*********************************************************************************************/
|
|
||||||
|
|
||||||
const size_t endpoints_max = 8; // we limit to 8 endpoints
|
const size_t endpoints_max = 8; // we limit to 8 endpoints
|
||||||
|
|
||||||
class Z_Device {
|
class Z_Device {
|
||||||
@ -58,9 +37,8 @@ public:
|
|||||||
char * modelId;
|
char * modelId;
|
||||||
char * friendlyName;
|
char * friendlyName;
|
||||||
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
|
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
|
||||||
// json buffer used for attribute reporting
|
// Used for attribute reporting
|
||||||
DynamicJsonBuffer *json_buffer;
|
Z_attribute_list attr_list;
|
||||||
JsonObject *json;
|
|
||||||
// sequence number for Zigbee frames
|
// sequence number for Zigbee frames
|
||||||
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
uint16_t shortaddr; // unique key if not null, or unspecified if null
|
||||||
uint8_t seqNumber;
|
uint8_t seqNumber;
|
||||||
@ -95,14 +73,13 @@ public:
|
|||||||
int16_t mains_power; // Active power
|
int16_t mains_power; // Active power
|
||||||
|
|
||||||
// Constructor with all defaults
|
// Constructor with all defaults
|
||||||
Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00):
|
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
|
||||||
longaddr(_longaddr),
|
longaddr(_longaddr),
|
||||||
manufacturerId(nullptr),
|
manufacturerId(nullptr),
|
||||||
modelId(nullptr),
|
modelId(nullptr),
|
||||||
friendlyName(nullptr),
|
friendlyName(nullptr),
|
||||||
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
|
||||||
json_buffer(nullptr),
|
attr_list(),
|
||||||
json(nullptr),
|
|
||||||
shortaddr(_shortaddr),
|
shortaddr(_shortaddr),
|
||||||
seqNumber(0),
|
seqNumber(0),
|
||||||
// Hue support
|
// Hue support
|
||||||
@ -207,7 +184,7 @@ typedef struct Z_Deferred {
|
|||||||
// - shortaddr and longaddr cannot be both null
|
// - shortaddr and longaddr cannot be both null
|
||||||
class Z_Devices {
|
class Z_Devices {
|
||||||
public:
|
public:
|
||||||
Z_Devices() {};
|
Z_Devices() : _deferred() {};
|
||||||
|
|
||||||
// Probe the existence of device keys
|
// Probe the existence of device keys
|
||||||
// Results:
|
// Results:
|
||||||
@ -218,12 +195,17 @@ public:
|
|||||||
uint16_t isKnownIndex(uint32_t index) const;
|
uint16_t isKnownIndex(uint32_t index) const;
|
||||||
uint16_t isKnownFriendlyName(const char * name) const;
|
uint16_t isKnownFriendlyName(const char * name) const;
|
||||||
|
|
||||||
|
Z_Device & findShortAddr(uint16_t shortaddr);
|
||||||
const Z_Device & findShortAddr(uint16_t shortaddr) const;
|
const Z_Device & findShortAddr(uint16_t shortaddr) const;
|
||||||
|
Z_Device & findLongAddr(uint64_t longaddr);
|
||||||
|
const Z_Device & findLongAddr(uint64_t longaddr) const;
|
||||||
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
|
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
|
||||||
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist
|
|
||||||
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
|
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
|
||||||
|
// check if a device was found or if it's the fallback device
|
||||||
|
inline bool foundDevice(const Z_Device & device) const {
|
||||||
|
return (&device != &device_unk);
|
||||||
|
}
|
||||||
|
|
||||||
int32_t findLongAddr(uint64_t longaddr) const;
|
|
||||||
int32_t findFriendlyName(const char * name) const;
|
int32_t findFriendlyName(const char * name) const;
|
||||||
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
|
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
|
||||||
|
|
||||||
@ -281,19 +263,22 @@ public:
|
|||||||
void runTimer(void);
|
void runTimer(void);
|
||||||
|
|
||||||
// Append or clear attributes Json structure
|
// Append or clear attributes Json structure
|
||||||
void jsonClear(uint16_t shortaddr);
|
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
|
||||||
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
|
|
||||||
const JsonObject *jsonGet(uint16_t shortaddr);
|
|
||||||
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
|
||||||
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
|
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
|
||||||
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
|
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
|
||||||
|
|
||||||
// Iterator
|
// Iterator
|
||||||
size_t devicesSize(void) const {
|
size_t devicesSize(void) const {
|
||||||
return _devices.size();
|
return _devices.length();
|
||||||
}
|
}
|
||||||
const Z_Device & devicesAt(size_t i) const {
|
const Z_Device & devicesAt(size_t i) const {
|
||||||
return *(_devices.at(i));
|
const Z_Device * devp = _devices.at(i);
|
||||||
|
if (devp) {
|
||||||
|
return *devp;
|
||||||
|
} else {
|
||||||
|
return device_unk;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove device from list
|
// Remove device from list
|
||||||
@ -308,8 +293,8 @@ public:
|
|||||||
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
|
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Z_Device*> _devices = {};
|
LList<Z_Device> _devices; // list of devices
|
||||||
std::vector<Z_Deferred> _deferred = {}; // list of deferred calls
|
LList<Z_Deferred> _deferred; // list of deferred calls
|
||||||
uint32_t _saveTimer = 0;
|
uint32_t _saveTimer = 0;
|
||||||
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
|
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
|
||||||
|
|
||||||
@ -317,10 +302,7 @@ private:
|
|||||||
// Any find() function will not return Null, instead it will return this instance
|
// Any find() function will not return Null, instead it will return this instance
|
||||||
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
|
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
|
||||||
|
|
||||||
template < typename T>
|
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
|
||||||
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
|
|
||||||
|
|
||||||
int32_t findShortAddrIdx(uint16_t shortaddr) const;
|
|
||||||
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
// Create a new entry in the devices list - must be called if it is sure it does not already exist
|
||||||
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
|
||||||
void freeDeviceEntry(Z_Device *device);
|
void freeDeviceEntry(Z_Device *device);
|
||||||
@ -343,31 +325,16 @@ uint16_t localShortAddr = 0;
|
|||||||
* Implementation
|
* Implementation
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
|
|
||||||
template < typename T>
|
|
||||||
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
|
|
||||||
// Find given element in vector
|
|
||||||
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
|
|
||||||
|
|
||||||
if (it != vecOfElements.end()) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
|
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
|
||||||
// entry with same shortaddr or longaddr exists.
|
// entry with same shortaddr or longaddr exists.
|
||||||
//
|
//
|
||||||
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
|
||||||
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
|
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
|
||||||
Z_Device * device_alloc = new Z_Device(shortaddr, longaddr);
|
Z_Device device(shortaddr, longaddr);
|
||||||
|
|
||||||
device_alloc->json_buffer = new DynamicJsonBuffer(16);
|
|
||||||
_devices.push_back(device_alloc);
|
|
||||||
dirty();
|
dirty();
|
||||||
return *(_devices.back());
|
return _devices.addHead(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Z_Devices::freeDeviceEntry(Z_Device *device) {
|
void Z_Devices::freeDeviceEntry(Z_Device *device) {
|
||||||
@ -383,20 +350,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
|
|||||||
// In:
|
// In:
|
||||||
// shortaddr (not BAD_SHORTADDR)
|
// shortaddr (not BAD_SHORTADDR)
|
||||||
// Out:
|
// Out:
|
||||||
// index in _devices of entry, -1 if not found
|
// reference to device, or to device_unk if not found
|
||||||
//
|
// (use foundDevice() to check if found)
|
||||||
int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const {
|
Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) {
|
||||||
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
|
|
||||||
int32_t found = 0;
|
|
||||||
for (auto & elem : _devices) {
|
for (auto & elem : _devices) {
|
||||||
if (elem->shortaddr == shortaddr) { return found; }
|
if (elem.shortaddr == shortaddr) { return elem; }
|
||||||
found++;
|
|
||||||
}
|
}
|
||||||
return -1;
|
return (Z_Device&) device_unk;
|
||||||
}
|
}
|
||||||
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
||||||
for (auto &elem : _devices) {
|
for (const auto & elem : _devices) {
|
||||||
if (elem->shortaddr == shortaddr) { return *elem; }
|
if (elem.shortaddr == shortaddr) { return elem; }
|
||||||
}
|
}
|
||||||
return device_unk;
|
return device_unk;
|
||||||
}
|
}
|
||||||
@ -408,14 +372,19 @@ const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
|
|||||||
// Out:
|
// Out:
|
||||||
// index in _devices of entry, -1 if not found
|
// index in _devices of entry, -1 if not found
|
||||||
//
|
//
|
||||||
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
|
Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) {
|
||||||
if (!longaddr) { return -1; }
|
if (!longaddr) { return (Z_Device&) device_unk; }
|
||||||
int32_t found = 0;
|
|
||||||
for (auto &elem : _devices) {
|
for (auto &elem : _devices) {
|
||||||
if (elem->longaddr == longaddr) { return found; }
|
if (elem.longaddr == longaddr) { return elem; }
|
||||||
found++;
|
|
||||||
}
|
}
|
||||||
return -1;
|
return (Z_Device&) device_unk;
|
||||||
|
}
|
||||||
|
const Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) const {
|
||||||
|
if (!longaddr) { return device_unk; }
|
||||||
|
for (const auto &elem : _devices) {
|
||||||
|
if (elem.longaddr == longaddr) { return elem; }
|
||||||
|
}
|
||||||
|
return device_unk;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// Scan all devices to find a corresponding friendlyNme
|
// Scan all devices to find a corresponding friendlyNme
|
||||||
@ -431,8 +400,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
|
|||||||
int32_t found = 0;
|
int32_t found = 0;
|
||||||
if (name_len) {
|
if (name_len) {
|
||||||
for (auto &elem : _devices) {
|
for (auto &elem : _devices) {
|
||||||
if (elem->friendlyName) {
|
if (elem.friendlyName) {
|
||||||
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
|
if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
|
||||||
}
|
}
|
||||||
found++;
|
found++;
|
||||||
}
|
}
|
||||||
@ -441,9 +410,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
|
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
|
||||||
int32_t found = findLongAddr(longaddr);
|
const Z_Device & device = findLongAddr(longaddr);
|
||||||
if (found >= 0) {
|
if (foundDevice(device)) {
|
||||||
const Z_Device & device = devicesAt(found);
|
|
||||||
return device.shortaddr; // can be zero, if not yet registered
|
return device.shortaddr; // can be zero, if not yet registered
|
||||||
} else {
|
} else {
|
||||||
return BAD_SHORTADDR;
|
return BAD_SHORTADDR;
|
||||||
@ -471,7 +439,7 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
||||||
return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
|
return findShortAddr(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -479,38 +447,28 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
|
|||||||
//
|
//
|
||||||
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
|
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
|
||||||
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
|
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (found >= 0) {
|
if (foundDevice(device)) {
|
||||||
return *(_devices[found]);
|
return device;
|
||||||
}
|
}
|
||||||
//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found);
|
|
||||||
return createDeviceEntry(shortaddr, 0);
|
return createDeviceEntry(shortaddr, 0);
|
||||||
}
|
}
|
||||||
// Same version but Const
|
|
||||||
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
|
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
|
||||||
if (found >= 0) {
|
|
||||||
return *(_devices[found]);
|
|
||||||
}
|
|
||||||
return device_unk;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find the Device object by its longaddr (unique key if not null)
|
// find the Device object by its longaddr (unique key if not null)
|
||||||
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
|
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
|
||||||
if (!longaddr) { return (Z_Device&) device_unk; }
|
if (!longaddr) { return (Z_Device&) device_unk; }
|
||||||
int32_t found = findLongAddr(longaddr);
|
Z_Device & device = findLongAddr(longaddr);
|
||||||
if (found > 0) {
|
if (foundDevice(device)) {
|
||||||
return *(_devices[found]);
|
return device;
|
||||||
}
|
}
|
||||||
return createDeviceEntry(0, longaddr);
|
return createDeviceEntry(0, longaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove device from list, return true if it was known, false if it was not recorded
|
// Remove device from list, return true if it was known, false if it was not recorded
|
||||||
bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (found >= 0) {
|
if (foundDevice(device)) {
|
||||||
freeDeviceEntry(_devices.at(found));
|
_devices.remove(&device);
|
||||||
_devices.erase(_devices.begin() + found);
|
|
||||||
dirty();
|
dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -523,27 +481,27 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
|
|||||||
// shortaddr
|
// shortaddr
|
||||||
// longaddr (both can't be null at the same time)
|
// longaddr (both can't be null at the same time)
|
||||||
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
|
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
|
||||||
int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry
|
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
|
||||||
int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry
|
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
|
||||||
|
|
||||||
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
|
if (foundDevice(*s_found) && foundDevice(*l_found)) { // both shortaddr and longaddr are already registered
|
||||||
if (s_found == l_found) {
|
if (s_found == l_found) {
|
||||||
} else { // they don't match
|
} else { // they don't match
|
||||||
// the device with longaddr got a new shortaddr
|
// the device with longaddr got a new shortaddr
|
||||||
_devices[l_found]->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
l_found->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
|
||||||
// erase the previous shortaddr
|
// erase the previous shortaddr
|
||||||
freeDeviceEntry(_devices.at(s_found));
|
freeDeviceEntry(s_found);
|
||||||
_devices.erase(_devices.begin() + s_found);
|
_devices.remove(s_found);
|
||||||
dirty();
|
dirty();
|
||||||
}
|
}
|
||||||
} else if (s_found >= 0) {
|
} else if (foundDevice(*s_found)) {
|
||||||
// shortaddr already exists but longaddr not
|
// shortaddr already exists but longaddr not
|
||||||
// add the longaddr to the entry
|
// add the longaddr to the entry
|
||||||
_devices[s_found]->longaddr = longaddr;
|
s_found->longaddr = longaddr;
|
||||||
dirty();
|
dirty();
|
||||||
} else if (l_found >= 0) {
|
} else if (foundDevice(*l_found)) {
|
||||||
// longaddr entry exists, update shortaddr
|
// longaddr entry exists, update shortaddr
|
||||||
_devices[l_found]->shortaddr = shortaddr;
|
l_found->shortaddr = shortaddr;
|
||||||
dirty();
|
dirty();
|
||||||
} else {
|
} else {
|
||||||
// neither short/lonf addr are found.
|
// neither short/lonf addr are found.
|
||||||
@ -588,9 +546,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
|
|||||||
//
|
//
|
||||||
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
|
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
|
||||||
uint32_t count_ep = 0;
|
uint32_t count_ep = 0;
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
const Z_Device & device =findShortAddr(shortaddr);
|
||||||
if (found < 0) return 0; // avoid creating an entry if the device was never seen
|
if (!foundDevice(device)) return 0;
|
||||||
const Z_Device &device = devicesAt(found);
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < endpoints_max; i++) {
|
for (uint32_t i = 0; i < endpoints_max; i++) {
|
||||||
if (0 != device.endpoints[i]) {
|
if (0 != device.endpoints[i]) {
|
||||||
@ -666,9 +623,8 @@ void Z_Devices::setBatteryPercent(uint16_t shortaddr, uint8_t bp) {
|
|||||||
|
|
||||||
// get the next sequance number for the device, or use the global seq number if device is unknown
|
// get the next sequance number for the device, or use the global seq number if device is unknown
|
||||||
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
|
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
|
||||||
int32_t short_found = findShortAddrIdx(shortaddr);
|
Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (short_found >= 0) {
|
if (foundDevice(device)) {
|
||||||
Z_Device &device = getShortAddr(shortaddr);
|
|
||||||
device.seqNumber += 1;
|
device.seqNumber += 1;
|
||||||
return device.seqNumber;
|
return device.seqNumber;
|
||||||
} else {
|
} else {
|
||||||
@ -754,9 +710,9 @@ void Z_Devices::hideHueBulb(uint16_t shortaddr, bool hidden) {
|
|||||||
}
|
}
|
||||||
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
|
// true if device is not knwon or not a bulb - it wouldn't make sense to publish a non-bulb
|
||||||
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
|
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
const Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (found >= 0) {
|
if (foundDevice(device)) {
|
||||||
uint8_t zb_profile = _devices[found]->zb_profile;
|
uint8_t zb_profile = device.zb_profile;
|
||||||
if (0x00 == (zb_profile & 0xF0)) {
|
if (0x00 == (zb_profile & 0xF0)) {
|
||||||
// bulb type
|
// bulb type
|
||||||
return (zb_profile & 0x08) ? true : false;
|
return (zb_profile & 0x08) ? true : false;
|
||||||
@ -769,13 +725,10 @@ bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
|
|||||||
// Parse for a specific category, of all deferred for a device if category == 0xFF
|
// 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) {
|
void Z_Devices::resetTimersForDevice(uint16_t shortaddr, uint16_t groupaddr, uint8_t category) {
|
||||||
// iterate the list of deferred, and remove any linked to the shortaddr
|
// iterate the list of deferred, and remove any linked to the shortaddr
|
||||||
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
|
for (auto & defer : _deferred) {
|
||||||
// Notice that the iterator is decremented after it is passed
|
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
|
||||||
// to erase() but before erase() is executed
|
if ((0xFF == category) || (defer.category == category)) {
|
||||||
// see https://www.techiedelight.com/remove-elements-vector-inside-loop-cpp/
|
_deferred.remove(&defer);
|
||||||
if ((it->shortaddr == shortaddr) && (it->groupaddr == groupaddr)) {
|
|
||||||
if ((0xFF == category) || (it->category == category)) {
|
|
||||||
_deferred.erase(it--);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -789,7 +742,8 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now create the new timer
|
// Now create the new timer
|
||||||
Z_Deferred deferred = { wait_ms + millis(), // timer
|
Z_Deferred & deferred = _deferred.addHead();
|
||||||
|
deferred = { wait_ms + millis(), // timer
|
||||||
shortaddr,
|
shortaddr,
|
||||||
groupaddr,
|
groupaddr,
|
||||||
cluster,
|
cluster,
|
||||||
@ -797,20 +751,17 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
|
|||||||
category,
|
category,
|
||||||
value,
|
value,
|
||||||
func };
|
func };
|
||||||
_deferred.push_back(deferred);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run timer at each tick
|
// Run timer at each tick
|
||||||
// WARNING: don't set a new timer within a running timer, this causes memory corruption
|
// WARNING: don't set a new timer within a running timer, this causes memory corruption
|
||||||
void Z_Devices::runTimer(void) {
|
void Z_Devices::runTimer(void) {
|
||||||
// visit all timers
|
// visit all timers
|
||||||
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
|
for (auto & defer : _deferred) {
|
||||||
Z_Deferred &defer = *it;
|
|
||||||
|
|
||||||
uint32_t timer = defer.timer;
|
uint32_t timer = defer.timer;
|
||||||
if (TimeReached(timer)) {
|
if (TimeReached(timer)) {
|
||||||
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
|
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
|
||||||
_deferred.erase(it--); // remove from list
|
_deferred.remove(&defer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -821,160 +772,86 @@ void Z_Devices::runTimer(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the JSON buffer for coalesced and deferred attributes
|
|
||||||
void Z_Devices::jsonClear(uint16_t shortaddr) {
|
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
|
||||||
device.json = nullptr;
|
|
||||||
device.json_buffer->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy JSON from one object to another, this helps preserving the order of attributes
|
|
||||||
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
|
|
||||||
// first remove the potentially existing key in the target JSON, so new adds will be at the end of the list
|
|
||||||
to.remove(key); // force remove to have metadata like LinkQuality at the end
|
|
||||||
|
|
||||||
if (val.is<char*>()) {
|
|
||||||
const char * sval = val.as<char*>(); // using char* forces a copy, and also captures 'null' values
|
|
||||||
to.set(key, (char*) sval);
|
|
||||||
} else if (val.is<JsonArray>()) {
|
|
||||||
JsonArray &nested_arr = to.createNestedArray(key);
|
|
||||||
CopyJsonArray(nested_arr, val.as<JsonArray>()); // deep copy
|
|
||||||
} else if (val.is<JsonObject>()) {
|
|
||||||
JsonObject &nested_obj = to.createNestedObject(key);
|
|
||||||
CopyJsonObject(nested_obj, val.as<JsonObject>()); // deep copy
|
|
||||||
} else {
|
|
||||||
to.set(key, val); // general case for non array, object or string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shallow copy of array, we skip any sub-array or sub-object. It may be added in the future
|
|
||||||
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
|
|
||||||
for (auto v : arr) {
|
|
||||||
if (v.is<char*>()) {
|
|
||||||
String sval = v.as<String>(); // force a copy of the String value
|
|
||||||
to.add(sval);
|
|
||||||
} else if (v.is<JsonArray>()) {
|
|
||||||
} else if (v.is<JsonObject>()) {
|
|
||||||
} else {
|
|
||||||
to.add(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deep copy of object
|
|
||||||
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
|
|
||||||
for (auto kv : from) {
|
|
||||||
String key_string = kv.key;
|
|
||||||
JsonVariant &val = kv.value;
|
|
||||||
|
|
||||||
CopyJsonVariant(to, key_string, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
|
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
|
||||||
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
|
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
|
||||||
// false - new attributes can be safely added
|
// false - new attributes can be safely added
|
||||||
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
|
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
const Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (&values == nullptr) { return false; }
|
|
||||||
|
|
||||||
if (nullptr == device.json) {
|
if (!foundDevice(device)) { return false; }
|
||||||
|
if (attr_list.isEmpty()) {
|
||||||
return false; // if no previous value, no conflict
|
return false; // if no previous value, no conflict
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare groups
|
// compare groups
|
||||||
// Special case for group addresses. Group attribute is only present if the target
|
if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) {
|
||||||
// address is a group address, so just comparing attributes will not work.
|
if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
|
||||||
// Eg: if the first packet has no group attribute, and the second does, conflict would not be detected
|
|
||||||
// Here we explicitly compute the group address of both messages, and compare them. No group means group=0x0000
|
|
||||||
// (we use the property of an missing attribute returning 0)
|
|
||||||
// (note: we use .get() here which is case-sensitive. We know however that the attribute was set with the exact syntax D_CMND_ZIGBEE_GROUP, so we don't need a case-insensitive get())
|
|
||||||
uint16_t group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
|
|
||||||
uint16_t group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
|
|
||||||
if (group1 != group2) {
|
|
||||||
return true; // if group addresses differ, then conflict
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compare src_ep
|
||||||
|
if (device.attr_list.isValidSrcEp() && attr_list.isValidSrcEp()) {
|
||||||
|
if (device.attr_list.src_ep != attr_list.src_ep) { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// LQI does not count as conflicting
|
||||||
|
|
||||||
// parse all other parameters
|
// parse all other parameters
|
||||||
for (auto kv : values) {
|
for (const auto & attr : attr_list) {
|
||||||
String key_string = kv.key;
|
const Z_attribute * curr_attr = device.attr_list.findAttribute(attr);
|
||||||
|
if (nullptr != curr_attr) {
|
||||||
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
|
if (!curr_attr->equalsVal(attr)) {
|
||||||
// ignore group, it was handled already
|
return true; // the value already exists and is different - conflict!
|
||||||
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
|
|
||||||
// attribute "Endpoint" or "Group"
|
|
||||||
if (device.json->containsKey(kv.key)) {
|
|
||||||
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
|
|
||||||
if (device.json->containsKey(kv.key)) {
|
|
||||||
return true; // conflict!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
|
void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (&values == nullptr) { return; }
|
device.attr_list.mergeList(attr_list);
|
||||||
|
|
||||||
if (nullptr == device.json) {
|
|
||||||
device.json = &(device.json_buffer->createObject());
|
|
||||||
}
|
|
||||||
// Prepend Device, will be removed later if redundant
|
|
||||||
char sa[8];
|
|
||||||
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
|
|
||||||
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
|
|
||||||
// Prepend Friendly Name if it has one
|
|
||||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
|
||||||
if (fname) {
|
|
||||||
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*) fname); // (char*) forces ArduinoJson to make a copy of the cstring
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy all values from 'values' to 'json'
|
|
||||||
CopyJsonObject(*device.json, values);
|
|
||||||
}
|
|
||||||
|
|
||||||
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
|
|
||||||
return getShortAddr(shortaddr).json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
||||||
Z_Device & device = getShortAddr(shortaddr);
|
Z_Device & device = getShortAddr(shortaddr);
|
||||||
if (!device.valid()) { return; } // safeguard
|
if (!device.valid()) { return; } // safeguard
|
||||||
JsonObject & json = *device.json;
|
Z_attribute_list &attr_list = device.attr_list;
|
||||||
if (&json == nullptr) { return; } // abort if nothing in buffer
|
|
||||||
|
|
||||||
|
if (!attr_list.isEmpty()) {
|
||||||
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
|
||||||
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||||
|
|
||||||
// save parameters is global variables to be used by Rules
|
// save parameters is global variables to be used by Rules
|
||||||
gZbLastMessage.device = shortaddr; // %zbdevice%
|
gZbLastMessage.device = shortaddr; // %zbdevice%
|
||||||
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
|
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
|
||||||
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
|
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
|
||||||
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
|
|
||||||
|
|
||||||
// dump json in string
|
|
||||||
String msg = "";
|
|
||||||
json.printTo(msg);
|
|
||||||
zigbee_devices.jsonClear(shortaddr);
|
|
||||||
|
|
||||||
|
mqtt_data[0] = 0; // clear string
|
||||||
|
// Do we prefix with `ZbReceived`?
|
||||||
|
if (!Settings.flag4.remove_zbreceived) {
|
||||||
|
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
|
||||||
|
}
|
||||||
|
// What key do we use, shortaddr or name?
|
||||||
if (use_fname) {
|
if (use_fname) {
|
||||||
if (Settings.flag4.remove_zbreceived) {
|
Response_P(PSTR("%s{\"%s\":{"), mqtt_data, fname);
|
||||||
Response_P(PSTR("{\"%s\":%s}"), fname, msg.c_str());
|
|
||||||
} else {
|
} else {
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname, msg.c_str());
|
Response_P(PSTR("%s{\"0x%04X\":{"), mqtt_data, shortaddr);
|
||||||
}
|
}
|
||||||
} else {
|
// Add "Device":"0x...."
|
||||||
if (Settings.flag4.remove_zbreceived) {
|
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), mqtt_data, shortaddr);
|
||||||
Response_P(PSTR("{\"0x%04X\":%s}"), shortaddr, msg.c_str());
|
// Add "Name":"xxx" if name is present
|
||||||
} else {
|
if (fname) {
|
||||||
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
|
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), mqtt_data, EscapeJSONString(fname).c_str());
|
||||||
}
|
}
|
||||||
|
// Add all other attributes
|
||||||
|
Response_P(PSTR("%s%s}}"), mqtt_data, attr_list.toString().c_str());
|
||||||
|
|
||||||
|
if (!Settings.flag4.remove_zbreceived) {
|
||||||
|
Response_P(PSTR("%s}"), mqtt_data);
|
||||||
}
|
}
|
||||||
|
// AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> %s"), mqtt_data); // TODO
|
||||||
|
attr_list.reset(); // clear the attributes
|
||||||
|
|
||||||
if (Settings.flag4.zigbee_distinct_topics) {
|
if (Settings.flag4.zigbee_distinct_topics) {
|
||||||
char subtopic[16];
|
char subtopic[16];
|
||||||
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
|
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
|
||||||
@ -984,10 +861,11 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
|
|||||||
}
|
}
|
||||||
XdrvRulesProcess(); // apply rules
|
XdrvRulesProcess(); // apply rules
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
|
void Z_Devices::jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list) {
|
||||||
jsonPublishFlush(shortaddr); // flush any previous buffer
|
jsonPublishFlush(shortaddr); // flush any previous buffer
|
||||||
jsonAppend(shortaddr, values);
|
jsonAppend(shortaddr, attr_list);
|
||||||
jsonPublishFlush(shortaddr); // publish now
|
jsonPublishFlush(shortaddr); // publish now
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1044,9 +922,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
|
|||||||
JsonObject& json = jsonBuffer.createObject();
|
JsonObject& json = jsonBuffer.createObject();
|
||||||
char hex[8];
|
char hex[8];
|
||||||
|
|
||||||
int32_t found = findShortAddrIdx(shortaddr);
|
const Z_Device & device = findShortAddr(shortaddr);
|
||||||
if (found >= 0) {
|
if (foundDevice(device)) {
|
||||||
const Z_Device & device = devicesAt(found);
|
|
||||||
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?
|
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
|
||||||
@ -1090,8 +967,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
|
|||||||
JsonArray& json = jsonBuffer.createArray();
|
JsonArray& json = jsonBuffer.createArray();
|
||||||
JsonArray& devices = json;
|
JsonArray& devices = json;
|
||||||
|
|
||||||
for (std::vector<Z_Device*>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
|
for (const auto & device : _devices) {
|
||||||
const Z_Device &device = **it;
|
|
||||||
uint16_t shortaddr = device.shortaddr;
|
uint16_t shortaddr = device.shortaddr;
|
||||||
char hex[22];
|
char hex[22];
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -328,13 +328,8 @@ void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uin
|
|||||||
|
|
||||||
|
|
||||||
// Parse a cluster specific command, and try to convert into human readable
|
// Parse a cluster specific command, and try to convert into human readable
|
||||||
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
|
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
|
||||||
size_t hex_char_len = payload.len()*2+2;
|
const char * command_name = nullptr;
|
||||||
char *hex_char = (char*) malloc(hex_char_len);
|
|
||||||
if (!hex_char) { return; }
|
|
||||||
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
|
|
||||||
|
|
||||||
const __FlashStringHelper* command_name = nullptr;
|
|
||||||
uint8_t conv_direction;
|
uint8_t conv_direction;
|
||||||
Z_XYZ_Var xyz;
|
Z_XYZ_Var xyz;
|
||||||
|
|
||||||
@ -373,7 +368,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||||||
p += 2;
|
p += 2;
|
||||||
}
|
}
|
||||||
if (match) {
|
if (match) {
|
||||||
command_name = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->tasmota_cmd_offset));
|
command_name = Z_strings + pgm_read_word(&conv->tasmota_cmd_offset);
|
||||||
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
|
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
|
||||||
if (0xFF == conv_cmd) {
|
if (0xFF == conv_cmd) {
|
||||||
// shift all values
|
// shift all values
|
||||||
@ -396,112 +391,105 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
|
|||||||
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
|
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
|
||||||
char attrid_str[12];
|
char attrid_str[12];
|
||||||
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
|
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
|
||||||
json[attrid_str] = hex_char;
|
attr_list.addAttribute(attrid_str).setBuf(payload, 0, payload.len());
|
||||||
free(hex_char);
|
|
||||||
|
|
||||||
if (command_name) {
|
if (command_name) {
|
||||||
// Now try to transform into a human readable format
|
// Now try to transform into a human readable format
|
||||||
String command_name2 = String(command_name);
|
|
||||||
// if (direction & 0x80) then specific transform
|
// if (direction & 0x80) then specific transform
|
||||||
if (conv_direction & 0x80) {
|
if (conv_direction & 0x80) {
|
||||||
// TODO need to create a specific command
|
uint32_t cccc00mm = (cluster << 16) | cmd; // format = cccc00mm, cccc = cluster, mm = command
|
||||||
// IAS
|
// IAS
|
||||||
if ((cluster == 0x0500) && (cmd == 0x00)) {
|
switch (cccc00mm) {
|
||||||
// "ZoneStatusChange"
|
case 0x05000000: // "ZoneStatusChange"
|
||||||
json[command_name] = xyz.x;
|
attr_list.addAttribute(command_name, true).setUInt(xyz.x);
|
||||||
if (0 != xyz.y) {
|
if (0 != xyz.y) {
|
||||||
json[command_name2 + F("Ext")] = xyz.y;
|
attr_list.addAttribute(command_name, PSTR("Ext")).setUInt(xyz.y);
|
||||||
}
|
}
|
||||||
if ((0 != xyz.z) && (0xFF != xyz.z)) {
|
if ((0 != xyz.z) && (0xFF != xyz.z)) {
|
||||||
json[command_name2 + F("Zone")] = xyz.z;
|
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
|
||||||
}
|
}
|
||||||
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
|
break;
|
||||||
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
|
case 0x00040000:
|
||||||
json[command_name] = xyz.y;
|
case 0x00040001:
|
||||||
json[command_name2 + F("Status")] = xyz.x;
|
case 0x00040003: // AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
|
||||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
attr_list.addAttribute(command_name, true).setUInt(xyz.y);
|
||||||
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
|
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||||
// GetGroupResp
|
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||||
json[command_name2 + F("Capacity")] = xyz.x;
|
break;
|
||||||
json[command_name2 + F("Count")] = xyz.y;
|
case 0x00040002: // GetGroupResp
|
||||||
JsonArray &arr = json.createNestedArray(command_name);
|
attr_list.addAttribute(command_name, PSTR("Capacity")).setUInt(xyz.x);
|
||||||
|
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
|
||||||
|
{
|
||||||
|
|
||||||
|
Z_json_array group_list;
|
||||||
for (uint32_t i = 0; i < xyz.y; i++) {
|
for (uint32_t i = 0; i < xyz.y; i++) {
|
||||||
arr.add(payload.get16(2 + 2*i));
|
group_list.add(payload.get16(2 + 2*i));
|
||||||
}
|
}
|
||||||
} else if ((cluster == 0x0005) && ((cmd == 0x00) || (cmd == 0x02) || (cmd == 0x03))) {
|
attr_list.addAttribute(command_name, true).setStrRaw(group_list.toString().c_str());
|
||||||
// AddScene or RemoveScene or StoreScene
|
}
|
||||||
json[command_name2 + F("Status")] = xyz.x;
|
break;
|
||||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
case 0x00050000:
|
||||||
json[F("GroupId")] = xyz.y;
|
case 0x00050001: // ViewScene
|
||||||
json[F("SceneId")] = xyz.z;
|
case 0x00050002:
|
||||||
} else if ((cluster == 0x0005) && (cmd == 0x01)) {
|
case 0x00050004: // AddScene or RemoveScene or StoreScene
|
||||||
// ViewScene
|
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||||
json[command_name2 + F("Status")] = xyz.x;
|
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
|
||||||
json[F("GroupId")] = xyz.y;
|
attr_list.addAttribute(PSTR("SceneId"), true).setUInt(xyz.z);
|
||||||
json[F("SceneId")] = xyz.z;
|
if (0x00050001 == cccc00mm) { // ViewScene specific
|
||||||
String scene_payload = json[attrid_str];
|
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
|
||||||
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
|
}
|
||||||
} else if ((cluster == 0x0005) && (cmd == 0x03)) {
|
break;
|
||||||
// RemoveAllScenes
|
case 0x00050003: // RemoveAllScenes
|
||||||
json[command_name2 + F("Status")] = xyz.x;
|
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||||
json[F("GroupId")] = xyz.y;
|
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
|
||||||
} else if ((cluster == 0x0005) && (cmd == 0x06)) {
|
break;
|
||||||
// GetSceneMembership
|
case 0x00050006: // GetSceneMembership
|
||||||
json[command_name2 + F("Status")] = xyz.x;
|
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
|
||||||
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
|
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
|
||||||
json[F("Capacity")] = xyz.y;
|
attr_list.addAttribute(PSTR("Capacity"), true).setUInt(xyz.y);
|
||||||
json[F("GroupId")] = xyz.z;
|
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.z);
|
||||||
String scene_payload = json[attrid_str];
|
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
|
||||||
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
|
break;
|
||||||
} else if ((cluster == 0x0006) && (cmd == 0x40)) {
|
case 0x00060040: // Power Off With Effect
|
||||||
// Power Off With Effect
|
attr_list.addAttribute(PSTR("Power"), true).setUInt(0);
|
||||||
json[F("Power")] = 0; // always "Power":0
|
attr_list.addAttribute(PSTR("PowerEffect"), true).setUInt(xyz.x);
|
||||||
json[F("PowerEffect")] = xyz.x;
|
attr_list.addAttribute(PSTR("PowerEffectVariant"), true).setUInt(xyz.y);
|
||||||
json[F("PowerEffectVariant")] = xyz.y;
|
break;
|
||||||
} else if ((cluster == 0x0006) && (cmd == 0x41)) {
|
case 0x00060041: // Power On With Recall Global Scene
|
||||||
// Power On With Recall Global Scene
|
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
|
||||||
json[F("Power")] = 1; // always "Power":1
|
attr_list.addAttribute(PSTR("PowerRecallGlobalScene"), true).setBool(true);
|
||||||
json[F("PowerRecallGlobalScene")] = true;
|
break;
|
||||||
} else if ((cluster == 0x0006) && (cmd == 0x42)) {
|
case 0x00060042: // Power On With Timed Off Command
|
||||||
// Power On With Timed Off Command
|
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
|
||||||
json[F("Power")] = 1; // always "Power":1
|
attr_list.addAttribute(PSTR("PowerOnlyWhenOn"), true).setUInt(xyz.x);
|
||||||
json[F("PowerOnlyWhenOn")] = xyz.x;
|
attr_list.addAttribute(PSTR("PowerOnTime"), true).setFloat(xyz.y / 10.0f);
|
||||||
json[F("PowerOnTime")] = xyz.y / 10.0f;
|
attr_list.addAttribute(PSTR("PowerOffWait"), true).setFloat(xyz.z / 10.0f);
|
||||||
json[F("PowerOffWait")] = xyz.z / 10.0f;
|
break;
|
||||||
}
|
}
|
||||||
} else { // general case
|
} else { // general case
|
||||||
bool extended_command = false; // do we send command with endpoint suffix
|
// do we send command with endpoint suffix
|
||||||
|
char command_suffix[4] = { 0x00 }; // empty string by default
|
||||||
// if SO101 and multiple endpoints, append endpoint number
|
// if SO101 and multiple endpoints, append endpoint number
|
||||||
if (Settings.flag4.zb_index_ep) {
|
if (Settings.flag4.zb_index_ep) {
|
||||||
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
|
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
|
||||||
command_name2 += srcendpoint;
|
snprintf_P(command_suffix, sizeof(command_suffix), PSTR("%d"), srcendpoint);
|
||||||
extended_command = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (0 == xyz.x_type) {
|
if (0 == xyz.x_type) {
|
||||||
json[command_name] = true; // no parameter
|
attr_list.addAttribute(command_name, command_suffix).setBool(true);
|
||||||
if (extended_command) { json[command_name2] = true; }
|
|
||||||
} else if (0 == xyz.y_type) {
|
} else if (0 == xyz.y_type) {
|
||||||
json[command_name] = xyz.x; // 1 parameter
|
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
|
||||||
if (extended_command) { json[command_name2] = xyz.x; }
|
|
||||||
} else {
|
} else {
|
||||||
// multiple answers, create an array
|
// multiple answers, create an array
|
||||||
JsonArray &arr = json.createNestedArray(command_name);
|
Z_json_array arr;
|
||||||
arr.add(xyz.x);
|
arr.add(xyz.x);
|
||||||
arr.add(xyz.y);
|
arr.add(xyz.y);
|
||||||
if (xyz.z_type) {
|
if (xyz.z_type) {
|
||||||
arr.add(xyz.z);
|
arr.add(xyz.z);
|
||||||
}
|
}
|
||||||
if (extended_command) {
|
attr_list.addAttribute(command_name, command_suffix).setStrRaw(arr.toString().c_str());
|
||||||
JsonArray &arr = json.createNestedArray(command_name2);
|
|
||||||
arr.add(xyz.x);
|
|
||||||
arr.add(xyz.y);
|
|
||||||
if (xyz.z_type) {
|
|
||||||
arr.add(xyz.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1011,53 +1011,53 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) {
|
|||||||
// Parse incoming ZCL message.
|
// Parse incoming ZCL message.
|
||||||
//
|
//
|
||||||
// This code is common to ZNP and EZSP
|
// This code is common to ZNP and EZSP
|
||||||
void Z_IncomingMessage(ZCLFrame &zcl_received) {
|
void Z_IncomingMessage(class ZCLFrame &zcl_received) {
|
||||||
uint16_t srcaddr = zcl_received.getSrcAddr();
|
uint16_t srcaddr = zcl_received.getSrcAddr();
|
||||||
uint16_t groupid = zcl_received.getGroupAddr();
|
uint16_t groupid = zcl_received.getGroupAddr();
|
||||||
uint16_t clusterid = zcl_received.getClusterId();
|
uint16_t clusterid = zcl_received.getClusterId();
|
||||||
uint8_t linkquality = zcl_received.getLinkQuality();
|
uint8_t linkquality = zcl_received.getLinkQuality();
|
||||||
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
|
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
|
||||||
|
linkquality = linkquality != 0xFF ? linkquality : 0xFE; // avoid 0xFF (reserved for unknown)
|
||||||
|
|
||||||
bool defer_attributes = false; // do we defer attributes reporting to coalesce
|
bool defer_attributes = false; // do we defer attributes reporting to coalesce
|
||||||
|
|
||||||
// log the packet details
|
// log the packet details
|
||||||
zcl_received.log();
|
zcl_received.log();
|
||||||
|
|
||||||
zigbee_devices.setLQI(srcaddr, linkquality != 0xFF ? linkquality : 0xFE); // EFR32 has a different scale for LQI
|
zigbee_devices.setLQI(srcaddr, linkquality); // EFR32 has a different scale for LQI
|
||||||
|
|
||||||
char shortaddr[8];
|
char shortaddr[8];
|
||||||
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
|
||||||
|
|
||||||
DynamicJsonBuffer jsonBuffer;
|
Z_attribute_list attr_list;
|
||||||
JsonObject& json = jsonBuffer.createObject();
|
attr_list.lqi = linkquality;
|
||||||
|
attr_list.src_ep = srcendpoint;
|
||||||
|
if (groupid) { // TODO we miss the group_id == 0 here
|
||||||
|
attr_list.group_id = groupid;
|
||||||
|
}
|
||||||
|
|
||||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
|
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
|
zcl_received.parseResponse(); // Zigbee general "Default Response", publish ZbResponse message
|
||||||
} else {
|
} else {
|
||||||
// Build the ZbReceive json
|
// Build the ZbReceive list
|
||||||
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors
|
zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors
|
||||||
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
||||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseReadAttributesResponse(json);
|
zcl_received.parseReadAttributesResponse(attr_list);
|
||||||
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
|
||||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseReadAttributes(json);
|
zcl_received.parseReadAttributes(attr_list);
|
||||||
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
|
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
|
||||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseReadConfigAttributes(json);
|
zcl_received.parseReadConfigAttributes(attr_list);
|
||||||
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
|
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
|
||||||
zcl_received.parseConfigAttributes(json);
|
zcl_received.parseConfigAttributes(attr_list);
|
||||||
} else if (zcl_received.isClusterSpecificCommand()) {
|
} else if (zcl_received.isClusterSpecificCommand()) {
|
||||||
zcl_received.parseClusterSpecificCommand(json);
|
zcl_received.parseClusterSpecificCommand(attr_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // fence to force early de-allocation of msg
|
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str());
|
||||||
String msg("");
|
|
||||||
msg.reserve(100);
|
|
||||||
json.printTo(msg);
|
|
||||||
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// discard the message if it was sent by us (broadcast or group loopback)
|
// discard the message if it was sent by us (broadcast or group loopback)
|
||||||
if (srcaddr == localShortAddr) {
|
if (srcaddr == localShortAddr) {
|
||||||
@ -1065,37 +1065,25 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
|
|||||||
return; // abort the rest of message management
|
return; // abort the rest of message management
|
||||||
}
|
}
|
||||||
|
|
||||||
zcl_received.postProcessAttributes(srcaddr, json);
|
zcl_received.generateSyntheticAttributes(attr_list);
|
||||||
// Add Endpoint
|
zcl_received.generateCallBacks(attr_list); // set deferred callbacks, ex: Occupancy
|
||||||
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
|
zcl_received.postProcessAttributes(srcaddr, attr_list);
|
||||||
// Add Group if non-zero
|
|
||||||
if (groupid) {
|
|
||||||
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
|
|
||||||
}
|
|
||||||
// Add linkquality
|
|
||||||
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
|
|
||||||
|
|
||||||
// since we just receveived data from the device, it is reachable
|
// since we just receveived data from the device, it is reachable
|
||||||
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
|
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
|
||||||
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
|
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
|
||||||
|
|
||||||
// Post-provess for Aqara Presence Senson
|
|
||||||
Z_AqaraOccupancy(srcaddr, clusterid, srcendpoint, json);
|
|
||||||
|
|
||||||
if (defer_attributes) {
|
if (defer_attributes) {
|
||||||
// Prepare for publish
|
// Prepare for publish
|
||||||
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
|
if (zigbee_devices.jsonIsConflict(srcaddr, attr_list)) {
|
||||||
// there is conflicting values, force a publish of the previous message now and don't coalesce
|
// there is conflicting values, force a publish of the previous message now and don't coalesce
|
||||||
zigbee_devices.jsonPublishFlush(srcaddr);
|
zigbee_devices.jsonPublishFlush(srcaddr);
|
||||||
}
|
}
|
||||||
zigbee_devices.jsonAppend(srcaddr, json);
|
zigbee_devices.jsonAppend(srcaddr, attr_list);
|
||||||
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
|
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
|
||||||
} else {
|
} else {
|
||||||
// Publish immediately
|
// Publish immediately
|
||||||
zigbee_devices.jsonPublishNow(srcaddr, json);
|
zigbee_devices.jsonPublishNow(srcaddr, attr_list);
|
||||||
|
|
||||||
// Add auto-responder here
|
|
||||||
Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1281,30 +1269,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
|
|||||||
* Callbacks
|
* Callbacks
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
|
|
||||||
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
|
|
||||||
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
|
|
||||||
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
|
|
||||||
// Read OCCUPANCY value if any
|
|
||||||
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(OCCUPANCY));
|
|
||||||
if (nullptr != &val_endpoint) {
|
|
||||||
uint32_t occupancy = strToUInt(val_endpoint);
|
|
||||||
|
|
||||||
if (occupancy) {
|
|
||||||
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
|
|
||||||
} else {
|
|
||||||
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Publish the received values once they have been coalesced
|
// 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) {
|
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
|
||||||
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
|
|
||||||
if (json == nullptr) { return 0; } // don't crash if not found
|
|
||||||
|
|
||||||
zigbee_devices.jsonPublishFlush(shortaddr);
|
zigbee_devices.jsonPublishFlush(shortaddr);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -1476,24 +1442,26 @@ int32_t Z_State_Ready(uint8_t value) {
|
|||||||
//
|
//
|
||||||
// Mostly used for routers/end-devices
|
// Mostly used for routers/end-devices
|
||||||
// json: holds the attributes in JSON format
|
// json: holds the attributes in JSON format
|
||||||
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
|
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list, size_t attr_len) {
|
||||||
DynamicJsonBuffer jsonBuffer;
|
DynamicJsonBuffer jsonBuffer;
|
||||||
JsonObject& json_out = jsonBuffer.createObject();
|
JsonObject& json_out = jsonBuffer.createObject();
|
||||||
|
|
||||||
// responder
|
for (uint32_t i=0; i<attr_len; i++) {
|
||||||
switch (cluster) {
|
uint16_t attr = attr_list[i];
|
||||||
case 0x0000:
|
uint32_t ccccaaaa = (cluster << 16) || attr;
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); }
|
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); }
|
switch (ccccaaaa) {
|
||||||
break;
|
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
|
||||||
|
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
|
||||||
#ifdef USE_LIGHT
|
#ifdef USE_LIGHT
|
||||||
case 0x0006:
|
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; }
|
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
|
||||||
break;
|
|
||||||
case 0x0008:
|
case 0x03000000: // Hue
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); }
|
case 0x03000001: // Sat
|
||||||
break;
|
case 0x03000003: // X
|
||||||
case 0x0300:
|
case 0x03000004: // Y
|
||||||
|
case 0x03000007: // CT
|
||||||
{
|
{
|
||||||
uint16_t hue;
|
uint16_t hue;
|
||||||
uint8_t sat;
|
uint8_t sat;
|
||||||
@ -1505,20 +1473,30 @@ void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const
|
|||||||
uxy[i] = XY[i] * 65536.0f;
|
uxy[i] = XY[i] * 65536.0f;
|
||||||
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
|
||||||
}
|
}
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
|
if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
|
if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); }
|
if (0x0003 == attr) { json_out[F("X")] = uxy[0]; }
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; }
|
if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; }
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; }
|
if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); }
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case 0x000A: // Time
|
case 0x000A0000: // Time
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; }
|
json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time;
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("TimeEpoch"))) { json_out[F("TimeEpoch")] = Rtc.utc_time; }
|
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized
|
|
||||||
if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds
|
|
||||||
break;
|
break;
|
||||||
|
case 0x000AFF00: // TimeEpoch - Tasmota specific
|
||||||
|
json_out[F("TimeEpoch")] = Rtc.utc_time;
|
||||||
|
break;
|
||||||
|
case 0x000A0001: // TimeStatus
|
||||||
|
json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; // if time is beyond 2010 then we are synchronized
|
||||||
|
break;
|
||||||
|
case 0x000A0002: // TimeZone
|
||||||
|
json_out[F("TimeZone")] = Settings.toffset[0] * 60;
|
||||||
|
break;
|
||||||
|
case 0x000A0007: // LocalTime // TODO take DST
|
||||||
|
json_out[F("LocalTime")] = Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json_out.size() > 0) {
|
if (json_out.size() > 0) {
|
||||||
|
@ -795,7 +795,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind
|
|||||||
if (nullptr != &val_cluster) {
|
if (nullptr != &val_cluster) {
|
||||||
cluster = strToUInt(val_cluster); // first convert as number
|
cluster = strToUInt(val_cluster); // first convert as number
|
||||||
if (0 == cluster) {
|
if (0 == cluster) {
|
||||||
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr, nullptr);
|
zigbeeFindAttributeByName(val_cluster.as<const char*>(), &cluster, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,8 +155,14 @@ a_setoption = [[
|
|||||||
"Enable zerocross dimmer on PWM DIMMER",
|
"Enable zerocross dimmer on PWM DIMMER",
|
||||||
"Remove ZbReceived form JSON message",
|
"Remove ZbReceived form JSON message",
|
||||||
"Add the source endpoint as suffix to attributes",
|
"Add the source endpoint as suffix to attributes",
|
||||||
"","","","",
|
"Baud rate for Teleinfo communication (0 = 1200 or 1 = 9600)",
|
||||||
"","","","",
|
"TLS mode",
|
||||||
|
"Disable all MQTT retained messages",
|
||||||
|
"Enable White blend mode",
|
||||||
|
"Create a virtual White ColorTemp for RGBW lights",
|
||||||
|
"Select virtual White as (0) Warm or (1) Cold",
|
||||||
|
"Enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1)",
|
||||||
|
"Force gen1 Alexa mode",
|
||||||
"","","",""
|
"","","",""
|
||||||
],[
|
],[
|
||||||
"","","","",
|
"","","","",
|
||||||
@ -220,9 +226,9 @@ a_features = [[
|
|||||||
"USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075",
|
"USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075",
|
||||||
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
|
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
|
||||||
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
|
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
|
||||||
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","",
|
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","USE_I2S_AUDIO",
|
||||||
"","","","",
|
"","","","",
|
||||||
"","","USE_ETHERNET","USE_WEBCAM"
|
"","USE_TTGO_WATCH","USE_ETHERNET","USE_WEBCAM"
|
||||||
],[
|
],[
|
||||||
"","","","",
|
"","","","",
|
||||||
"","","","",
|
"","","","",
|
||||||
@ -259,7 +265,7 @@ else:
|
|||||||
obj = json.load(fp)
|
obj = json.load(fp)
|
||||||
|
|
||||||
def StartDecode():
|
def StartDecode():
|
||||||
print ("\n*** decode-status.py v20200817 by Theo Arends and Jacek Ziolkowski ***")
|
print ("\n*** decode-status.py v20200906 by Theo Arends and Jacek Ziolkowski ***")
|
||||||
|
|
||||||
# print("Decoding\n{}".format(obj))
|
# print("Decoding\n{}".format(obj))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user