Merge branch 'development' into patch-1

This commit is contained in:
Jason2866 2020-09-07 19:59:08 +02:00 committed by GitHub
commit c9715fa5b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
71 changed files with 3419 additions and 3581 deletions

View File

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

View File

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

View File

@ -21,7 +21,7 @@ While fallback or downgrading is common practice it was never supported due to S
## Supported Core versions
This release will be supported from ESP8266/Arduino library Core version **2.7.2.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
This release will be supported from ESP8266/Arduino library Core version **2.7.4.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
Support of Core versions before 2.7.1 has been removed.
@ -35,7 +35,7 @@ For initial configuration this release supports Webserver based **WifiManager**
## Provided Binary Downloads
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.2.1**.
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.4.1**.
- **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY**
- **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages.
@ -53,21 +53,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog
### Version 8.4.0.3
- Remove support for 1-step upgrade from versions before 6.6.0.11 to versions after 8.4.0.1
- Change references from http://thehackbox.org to http://ota.tasmota.com
- Change White blend mode moved to using ``SetOption 105`` instead of ``RGBWWTable``
- Fix ESP32 PWM range
- Fix display power control (#9114)
- Add command ``SetOption108 0/1`` to enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1) - Add Zigbee better support for IKEA Motion Sensor
- Add command ``SetOption109 1`` to force gen1 Alexa mode, for Echo Dot 2nd gen devices only
- Add command ``Restart 2`` to halt system. Needs hardware reset or power cycle to restart (#9046)
- Add ESP32 Analog input support for GPIO32 to GPIO39
- Add Zigbee options to ``ZbSend`` ``Config`` and ``ReadCondig``
- Add Zigbee web gui widget for Temp/Humidity/Pressure sensors
- Add Zigbee web ui for power metering plugs
- Add better config corruption recovery (#9046)
- Add virtual CT for 4 channels lights, emulating a 5th channel
- Add support for DYP ME007 ultrasonic distance sensor by Janusz Kostorz (#9113)
- Add command ``PowerDelta1`` to ``PowerDelta3`` to trigger on up to three phases (#9134)
### Version 8.5.0.1

File diff suppressed because it is too large Load Diff

View File

@ -546,7 +546,7 @@ void Arduino_ST7789::DisplayOnff(int8_t on) {
writecommand(ST7789_DISPON); //Display on
if (_bp>=0) {
#ifdef ST7789_DIMMER
ledcWrite(ESP32_PWM_CHANNEL,255);
ledcWrite(ESP32_PWM_CHANNEL,dimmer);
#else
digitalWrite(_bp,HIGH);
#endif
@ -564,7 +564,8 @@ void Arduino_ST7789::DisplayOnff(int8_t on) {
}
// dimmer 0-100
void Arduino_ST7789::dim(uint8_t dimmer) {
void Arduino_ST7789::dim(uint8_t dim) {
dimmer = dim;
if (dimmer>15) dimmer=15;
dimmer=((float)dimmer/15.0)*255.0;
#ifdef ESP32

View File

@ -161,7 +161,7 @@ class Arduino_ST7789 : public Renderer {
boolean _hwSPI;
boolean _SPI9bit;
boolean _DCbit;
uint8_t dimmer;
int8_t _cs, _dc, _rst, _sid, _sclk, _bp;
#if defined(USE_FAST_IO)

View File

@ -41,7 +41,7 @@ int FT5206_Class::begin(TwoWire &port, uint8_t addr)
}
_readByte(FT5206_CHIPID_REG, 1, &val);
//Serial.printf("chip id %d\n",val );
if ((val != FT6206_CHIPID) && (val != FT6236_CHIPID) && (val != FT6236U_CHIPID) && (val != FT5206U_CHIPID)) {
if ((val != FT6206_CHIPID) && (val != FT6236_CHIPID) && (val != FT6236U_CHIPID) && (val != FT5206U_CHIPID) && (val != FT5316_CHIPID) ) {
return false;
}
_init = true;

View File

@ -49,6 +49,8 @@ github:https://github.com/lewisxhe/FT5206_Library
#define FT6236U_CHIPID 0x64
#define FT5206U_CHIPID 0x64
#define FT5316_CHIPID 0x0a
#define DEVIDE_MODE 0x00
#define TD_STATUS 0x02
#define TOUCH1_XH 0x03

View File

@ -1,159 +0,0 @@
#include <Wire.h>
#include <FT6236.h>
/*
* This is a static library so we need to make sure we process stuff as quick as possible
* as we do not want it to interfere with the RTOS by delaying routines unnecessarily.
* So, no delay()'s etc and opto the code as much as possible.
* ^^^ Need to be on TODO list to go through and make sure everything is as opto as
* possible
*/
uint8_t FT6236buf[FT6236_BUFFER_SIZE];
uint8_t FT6236_i2c_addr = 0x38;
uint8_t lenLibVersion = 0;
uint8_t firmwareId = 0;
struct tbuttonregister {
uint16_t BUTTONID;
uint16_t xmin;
uint16_t xmax;
uint16_t ymin;
uint16_t ymax;
} buttonregister[FT6236_MAX_BUTTONS]; // we're limiting to 16 buttons for now - can reduce or increase later as needed.
uint8_t buttoncount = 0;
void FT6236flushbuttonregister(void) {
uint16_t bid;
for (bid=0;bid<FT6236_MAX_BUTTONS;bid++) {
buttonregister[bid].BUTTONID=0;
buttonregister[bid].xmin=0;
buttonregister[bid].xmax=0;
buttonregister[bid].ymin=0;
buttonregister[bid].ymax=0;
}
buttoncount=0;
}
void FT6236registerbutton(uint16_t buttonid,uint16_t xmin,uint16_t ymin,uint16_t xmax, uint16_t ymax) {
buttonregister[buttoncount].BUTTONID=buttonid;
buttonregister[buttoncount].xmin=xmin;
buttonregister[buttoncount].xmax=xmax;
buttonregister[buttoncount].ymin=ymin;
buttonregister[buttoncount].ymax=ymax;
buttoncount++;
}
uint16_t FT6236GetButtonMask(void) {
uint16 bid;
TouchLocation tl[2];
uint8_t count = FT6236readTouchLocation(tl,2);
if (count > 0) {
uint16_t x = tl[0].x;
uint16_t y = tl[0].y;
for (bid=0;bid<buttoncount;bid++) {
if (x >= buttonregister[bid].xmin) {
if (x <= buttonregister[bid].xmax) {
if (y >= buttonregister[bid].ymin) {
if (y <= buttonregister[bid].ymax) {
return buttonregister[bid].BUTTONID;
}
}
}
}
}
}
return 0;
}
void FT6236begin(uint8_t i2c_addr) {
FT6236_i2c_addr=i2c_addr;
Wire.begin();
FT6236writeTouchRegister(0,FT6236_MODE_NORMAL);
lenLibVersion = FT6236readTouchAddr(0x0a1, FT6236buf, 2 );
firmwareId = FT6236readTouchRegister( 0xa6 );
}
void FT6236writeTouchRegister(uint8_t reg, uint8_t val)
{
Wire.beginTransmission(FT6236_i2c_addr);
Wire.write(reg); // register 0
Wire.write(val); // value
Wire.endTransmission();
}
uint8_t FT6236readTouchRegister(uint8_t reg)
{
Wire.beginTransmission(FT6236_i2c_addr);
Wire.write(reg); // register 0
uint8_t retVal = Wire.endTransmission();
uint8_t returned = Wire.requestFrom(FT6236_i2c_addr,uint8_t(1)); // request 6 uint8_ts from slave device #2
if (Wire.available())
{
retVal = Wire.read();
}
return retVal;
}
uint8_t FT6236readTouchAddr( uint8_t regAddr, uint8_t * pBuf, uint8_t len )
{
Wire.beginTransmission(FT6236_i2c_addr);
Wire.write( regAddr ); // register 0
uint8_t retVal = Wire.endTransmission();
uint8_t returned = Wire.requestFrom(FT6236_i2c_addr, len); // request 1 bytes from slave device #2
uint8_t i;
for (i = 0; (i < len) && Wire.available(); i++) {
pBuf[i] = Wire.read();
}
return i;
}
uint8_t FT6236readTouchLocation( TouchLocation * pLoc, uint8_t num )
{
uint8_t retVal = 0;
uint8_t i;
uint8_t k;
do
{
if (!pLoc) break; // must have a buffer
if (!num) break; // must be able to take at least one
uint8_t status = FT6236readTouchRegister(2);
static uint8_t tbuf[40];
if ((status & 0x0f) == 0) break; // no points detected
uint8_t hitPoints = status & 0x0f;
FT6236readTouchAddr( 0x03, tbuf, hitPoints*6);
for (k=0,i = 0; (i < hitPoints*6)&&(k < num); k++, i += 6) {
pLoc[k].x = (tbuf[i+0] & 0x0f) << 8 | tbuf[i+1];
pLoc[k].y = (tbuf[i+2] & 0x0f) << 8 | tbuf[i+3];
}
retVal = k;
} while (0);
return retVal;
}
uint32_t FT6236dist(const TouchLocation & loc)
{
uint32_t retVal = 0;
uint32_t x = loc.x;
uint32_t y = loc.y;
retVal = x*x + y*y;
return retVal;
}
/*
uint32_t FT6236dist(const TouchLocation & loc1, const TouchLocation & loc2)
{
uint32_t retVal = 0;
uint32_t x = loc1.x - loc2.x;
uint32_t y = loc1.y - loc2.y;
retVal = sqrt(x*x + y*y);
return retVal;
}
*/
bool FT6236sameLoc( const TouchLocation & loc, const TouchLocation & loc2 )
{
return FT6236dist(loc,loc2) < 50;
}

View File

@ -1,28 +0,0 @@
#ifndef FT6236
#define FT6236
#define FT6236_MODE_NORMAL 0x00
#define FT6236_MODE_TEST 0x04
#define FT6236_MODE_SYSTEM 0x01
#define FT6236_BUFFER_SIZE 0x1E // 30 bytes buffer
#define FT6236_MAX_BUTTONS 1 // 50 buttons should be enough for just about any page
struct TouchLocation {
uint16_t y; // we swop x and y in position because we're using the screen in portrait mode
uint16_t x;
};
void FT6236flushbuttonregister(void);
void FT6236registerbutton(uint16_t buttonid,uint16_t xmin,uint16_t ymin,uint16_t xmax, uint16_t ymax);
uint16_t FT6236GetButtonMask(void);
void FT6236begin(uint8_t i2c_addr);
uint8_t FT6236readTouchRegister( uint8_t reg );
uint8_t FT6236readTouchLocation( TouchLocation * pLoc, uint8_t num );
uint8_t FT6236readTouchAddr( uint8_t regAddr, uint8_t * pBuf, uint8_t len );
void FT6236writeTouchRegister( uint8_t reg, uint8_t val);
uint32_t FT6236dist(const TouchLocation & loc);
uint32_t FT6236dist(const TouchLocation & loc1, const TouchLocation & loc2);
bool FT6236sameLoc( const TouchLocation & loc, const TouchLocation & loc2 );
#endif

View File

@ -519,4 +519,5 @@ void VButton::xdrawButton(bool inverted) {
wr_redir=0;
}
/* END OF FILE */

View File

@ -46,9 +46,23 @@ private:
uint8_t font;
};
typedef union {
uint8_t data;
struct {
uint8_t spare0 : 1;
uint8_t spare1 : 1;
uint8_t spare2 : 1;
uint8_t spare3 : 1;
uint8_t disable : 1;
uint8_t on_off : 1;
uint8_t is_pushbutton : 1;
uint8_t is_virtual : 1;
};
} TButton_State;
class VButton : public Adafruit_GFX_Button {
public:
uint8_t vpower;
TButton_State vpower;
void xdrawButton(bool inverted);
};

View File

@ -13,7 +13,9 @@ src_dir = tasmota
build_dir = .pioenvs
workspace_dir = .pioenvs
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
; *** Build/upload environment
@ -125,6 +127,6 @@ build_flags = -DUSE_IR_REMOTE_FULL
[core]
; *** Esp8266 Tasmota modified Arduino core based on core 2.7.4
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_flags = ${esp82xx_defaults.build_flags}

View File

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

39
platformio_tasmota32.ini Normal file
View File

@ -0,0 +1,39 @@
; *** BETA ESP32 Tasmota version ***
; *** expect the unexpected. Some features not working!!! ***
[common32]
platform = espressif32@2.0.0
platform_packages = tool-esptoolpy@1.20800.0
board = esp32dev
board_build.ldscript = esp32_out.ld
board_build.partitions = esp32_partition_app1984k_spiffs64k.csv
board_build.flash_mode = ${common.board_build.flash_mode}
board_build.f_flash = ${common.board_build.f_flash}
board_build.f_cpu = ${common.board_build.f_cpu}
build_unflags = ${esp_defaults.build_unflags}
-Wpointer-arith
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_resetmethod = ${common.upload_resetmethod}
upload_speed = 921600
extra_scripts = ${common.extra_scripts}
build_flags = ${esp_defaults.build_flags}
-D CORE_DEBUG_LEVEL=0
-D BUFFER_LENGTH=128
-D MQTT_MAX_PACKET_SIZE=1200
-D uint32=uint32_t
-D uint16=uint16_t
-D uint8=uint8_t
-D sint8_t=int8_t
-D sint32_t=int32_t
-D sint16_t=int16_t
-D memcpy_P=memcpy
-D memcmp_P=memcmp
lib_extra_dirs =
libesp32
lib_ignore =
cc1101

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "открит шум"
#define D_AS3935_DISTDET "открито смущение"
#define D_AS3935_INTNOEV "Прекъсване без Събитие!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "слушане..."
#define D_AS3935_ON "Вкл."
#define D_AS3935_OFF "Изкл."

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -809,10 +809,10 @@
#define D_SCRIPT_UPLOAD_FILES "Upload Dateien"
//xsns_67_as3935.ino
#define D_AS3935_GAIN "Rauschpegel:"
#define D_AS3935_GAIN "Umgebung:"
#define D_AS3935_ENERGY "Energie:"
#define D_AS3935_DISTANCE "Entfernung:"
#define D_AS3935_DISTURBER "Störsingal:"
#define D_AS3935_DISTURBER "Entstörer:"
#define D_AS3935_VRMS "µVrms:"
#define D_AS3935_APRX "ca.:"
#define D_AS3935_AWAY "entfernt"
@ -823,6 +823,8 @@
#define D_AS3935_NOISE "Rauschen entdeckt"
#define D_AS3935_DISTDET "Störer entdeckt"
#define D_AS3935_INTNOEV "Interrupt ohne Grund!"
#define D_AS3935_FLICKER "IRQ Pin flackert!"
#define D_AS3935_POWEROFF "Ausgeschaltet"
#define D_AS3935_NOMESS "lausche..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ Pin flicker!"
#define D_AS3935_POWEROFF "Powerd Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "Ruido detectado"
#define D_AS3935_DISTDET "Perturbancia detectada"
#define D_AS3935_INTNOEV "Interrupción sin evento!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "Escuchando..."
#define D_AS3935_ON "Encendido"
#define D_AS3935_OFF "Apagado"

View File

@ -1,18 +1,14 @@
/*
fr-FR.h - localization for French - France for Tasmota
Copyright (C) 2020 Olivier Francais
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/>.
*/
@ -28,7 +24,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v8.1.0.1
* Updated until v8.4.0.3
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -84,7 +80,7 @@
#define D_DISABLED "Désactivé"
#define D_DISTANCE "Distance"
#define D_DNS_SERVER "Serveur DNS"
#define D_DONE "Fait"
#define D_DONE "Terminé"
#define D_DST_TIME "DST"
#define D_ECO2 "eCO₂"
#define D_EMULATION "Émulation"
@ -99,8 +95,8 @@
#define D_FILE "Fichier"
#define D_FLOW_RATE "Débit"
#define D_FREE_MEMORY "Mémoire libre"
#define D_PSR_MAX_MEMORY "PS-RAM Memory"
#define D_PSR_FREE_MEMORY "PS-RAM free Memory"
#define D_PSR_MAX_MEMORY "Mémoire PS-RAM"
#define D_PSR_FREE_MEMORY "Mémoire PS-RAM libre"
#define D_FREQUENCY "Fréquence"
#define D_GAS "Gaz"
#define D_GATEWAY "Passerelle"
@ -304,7 +300,7 @@
#define D_OTHER_PARAMETERS "Autres paramètres"
#define D_TEMPLATE "Modèle"
#define D_ACTIVATE "Activer"
#define D_DEVICE_NAME "Device Name"
#define D_DEVICE_NAME "Nom de l'appareil"
#define D_WEB_ADMIN_PASSWORD "Mot de passe Web Admin"
#define D_MQTT_ENABLE "MQTT activé"
#define D_MQTT_TLS_ENABLE "MQTT TLS"
@ -351,8 +347,8 @@
#define D_UPLOAD_STARTED "Téléchargement lancé"
#define D_UPGRADE_STARTED "Mise à jour lancée"
#define D_UPLOAD_DONE "Téléchargement terminé"
#define D_UPLOAD_TRANSFER "Upload transfer"
#define D_TRANSFER_STARTED "Transfer started"
#define D_UPLOAD_TRANSFER "Transfert ZigBee"
#define D_TRANSFER_STARTED "Transfert lancé"
#define D_UPLOAD_ERR_1 "Aucun fichier sélectionné"
#define D_UPLOAD_ERR_2 "Espace insuffisant"
#define D_UPLOAD_ERR_3 "L'octet magique n'est pas 0xE9"
@ -486,7 +482,7 @@
// xsns_07_sht1x.ino
#define D_SENSOR_DID_NOT_ACK_COMMAND "Le capteur n'a pas acquitté la commande"
#define D_SHT1X_FOUND "SHT1X found"
#define D_SHT1X_FOUND "SHT1X trouvé"
// xsns_18_pms5003.ino
#define D_STANDARD_CONCENTRATION "CF-1 PM" // Standard Particle CF-1 Particle Matter
@ -533,24 +529,24 @@
#define D_TX20_WEST "O"
// xsns_53_sml.ino
#define D_TPWRIN "Energy Total-In"
#define D_TPWROUT "Energy Total-Out"
#define D_TPWRCURR "Active Power-In/Out"
#define D_TPWRCURR1 "Active Power-In p1"
#define D_TPWRCURR2 "Active Power-In p2"
#define D_TPWRCURR3 "Active Power-In p3"
#define D_Strom_L1 "Current L1"
#define D_Strom_L2 "Current L2"
#define D_Strom_L3 "Current L3"
#define D_Spannung_L1 "Voltage L1"
#define D_Spannung_L2 "Voltage L2"
#define D_Spannung_L3 "Voltage L3"
#define D_METERNR "Meter_number"
#define D_TPWRIN "Energie totale Entrée"
#define D_TPWROUT "Energie totale Sortie"
#define D_TPWRCURR "Puissance active E/S"
#define D_TPWRCURR1 "Puissance active Ent Ph1"
#define D_TPWRCURR2 "Puissance active Ent Ph2"
#define D_TPWRCURR3 "Puissance active Ent Ph3"
#define D_Strom_L1 "Courant Ph1"
#define D_Strom_L2 "Courant Ph2"
#define D_Strom_L3 "Courant Ph3"
#define D_Spannung_L1 "Tension Ph1"
#define D_Spannung_L2 "Tension Ph2"
#define D_Spannung_L3 "Tension Ph3"
#define D_METERNR "Numéro compteur"
#define D_METERSID "Service ID"
#define D_GasIN "Compteur"
#define D_H2oIN "Compteur"
#define D_StL1L2L3 "Current L1+L2+L3"
#define D_SpL1L2L3 "Voltage L1+L2+L3/3"
#define D_GasIN "Compteur Gaz"
#define D_H2oIN "Compteur Eau"
#define D_StL1L2L3 "Courant Ph1+Ph2+Ph3"
#define D_SpL1L2L3 "Tension (Ph1+Ph2+Ph3)/3"
// tasmota_template.h - keep them as short as possible to be able to fit them in GUI drop down box
#define D_SENSOR_NONE "Aucun"
@ -689,17 +685,17 @@
#define D_SENSOR_DYP_RX "DYP Rx"
#define D_SENSOR_ELECTRIQ_MOODL "MOODL Tx"
#define D_SENSOR_AS3935 "AS3935"
#define D_SENSOR_WINDMETER_SPEED "WindMeter Spd"
#define D_SENSOR_WINDMETER_SPEED "Anémomètre"
#define D_SENSOR_TELEINFO_RX "TInfo Rx"
#define D_SENSOR_TELEINFO_ENABLE "TInfo EN"
#define D_SENSOR_LMT01_PULSE "LMT01 Pulse"
#define D_SENSOR_ADC_INPUT "ADC Input"
#define D_SENSOR_TELEINFO_ENABLE "TInfo En"
#define D_SENSOR_LMT01_PULSE "LMT01 Impulsion"
#define D_SENSOR_ADC_INPUT "ADC Entrée"
#define D_SENSOR_ADC_TEMP "ADC Temp"
#define D_SENSOR_ADC_LIGHT "ADC Light"
#define D_SENSOR_ADC_BUTTON "ADC Button"
#define D_SENSOR_ADC_RANGE "ADC Range"
#define D_SENSOR_ADC_LIGHT "ADC Lumière"
#define D_SENSOR_ADC_BUTTON "ADC Bouton"
#define D_SENSOR_ADC_RANGE "ADC Distance"
#define D_SENSOR_ADC_CT_POWER "ADC CT Power"
#define D_SENSOR_ADC_JOYSTICK "ADC Joystick"
#define D_SENSOR_ADC_JOYSTICK "ADC Manette"
#define D_GPIO_WEBCAM_PWDN "CAM_PWDN"
#define D_GPIO_WEBCAM_RESET "CAM_RESET"
#define D_GPIO_WEBCAM_XCLK "CAM_XCLK"
@ -719,7 +715,6 @@
#define D_SENSOR_TCP_RXD "TCP Rx"
#define D_SENSOR_IEM3000_TX "iEM3000 TX"
#define D_SENSOR_IEM3000_RX "iEM3000 RX"
// Units
#define D_UNIT_AMPERE "A"
#define D_UNIT_CELSIUS "C"
@ -759,7 +754,6 @@
#define D_UNIT_WATT "W"
#define D_UNIT_WATTHOUR "Wh"
#define D_UNIT_WATT_METER_QUADRAT "W/m²"
//SDM220, SDM120, LE01MR
#define D_PHASE_ANGLE "Angle de phase"
#define D_IMPORT_ACTIVE "Énergie act conso"
@ -770,7 +764,6 @@
#define D_UNIT_KWARH "kVArh"
#define D_UNIT_ANGLE "°"
#define D_TOTAL_ACTIVE "Total Active"
//SOLAXX1
#define D_PV1_VOLTAGE "Tension PV1"
#define D_PV1_CURRENT "Courant PV1"
@ -794,47 +787,45 @@
#define D_SOLAX_ERROR_6 "Défaut Surchauffe"
#define D_SOLAX_ERROR_7 "Défaut Ventilateur"
#define D_SOLAX_ERROR_8 "Défaut Autre équipement"
//xdrv_10_scripter.ino
#define D_CONFIGURE_SCRIPT "Edit script"
#define D_SCRIPT "edit script"
#define D_SDCARD_UPLOAD "file upload"
#define D_SDCARD_DIR "sd card directory"
#define D_UPL_DONE "Done"
#define D_SCRIPT_CHARS_LEFT "chars left"
#define D_SCRIPT_CHARS_NO_MORE "no more chars"
#define D_CONFIGURE_SCRIPT "Éditer le script"
#define D_SCRIPT "édition du script"
#define D_SDCARD_UPLOAD "Envoi du fichier"
#define D_SDCARD_DIR "Dossier carte SD"
#define D_UPL_DONE "Terminé"
#define D_SCRIPT_CHARS_LEFT "car. restant"
#define D_SCRIPT_CHARS_NO_MORE "plus de car."
#define D_SCRIPT_DOWNLOAD "Download"
#define D_SCRIPT_ENABLE "script enable"
#define D_SCRIPT_UPLOAD "Upload"
#define D_SCRIPT_UPLOAD_FILES "Upload files"
#define D_SCRIPT_ENABLE "script actif"
#define D_SCRIPT_UPLOAD "Envoi"
#define D_SCRIPT_UPLOAD_FILES "Envoi de fichiers"
//xsns_67_as3935.ino
#define D_AS3935_GAIN "gain:"
#define D_AS3935_ENERGY "energy:"
#define D_AS3935_ENERGY "energie:"
#define D_AS3935_DISTANCE "distance:"
#define D_AS3935_DISTURBER "disturber:"
#define D_AS3935_DISTURBER "interférence:"
#define D_AS3935_VRMS "µVrms:"
#define D_AS3935_APRX "aprx.:"
#define D_AS3935_AWAY "away"
#define D_AS3935_LIGHT "lightning"
#define D_AS3935_OUT "lightning out of range"
#define D_AS3935_NOT "distance not determined"
#define D_AS3935_ABOVE "lightning overhead"
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"
#define D_AS3935_INDOORS "Indoors"
#define D_AS3935_OUTDOORS "Outdoors"
#define D_AS3935_CAL_FAIL "calibration failed"
#define D_AS3935_CAL_OK "calibration set to:"
#define D_AS3935_APRX "approx.:"
#define D_AS3935_AWAY "de distance"
#define D_AS3935_LIGHT "éclair"
#define D_AS3935_OUT "éclair éloigné"
#define D_AS3935_NOT "distance indéterminée"
#define D_AS3935_ABOVE "éclair trop intense"
#define D_AS3935_NOISE "bruit détecté"
#define D_AS3935_DISTDET "interférence détectée"
#define D_AS3935_INTNOEV "Interruption sans évenement!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "en écoute..."
#define D_AS3935_ON "Actif"
#define D_AS3935_OFF "Inactif"
#define D_AS3935_INDOORS "Intérieur"
#define D_AS3935_OUTDOORS "Extérieur"
#define D_AS3935_CAL_FAIL "défaut de calibration"
#define D_AS3935_CAL_OK "calibration établie à :"
//xsns_68_opentherm.ino
#define D_SENSOR_BOILER_OT_RX "OpenTherm RX"
#define D_SENSOR_BOILER_OT_TX "OpenTherm TX"
// xnrg_15_teleinfo Denky (Teleinfo)
#define D_CONTRACT "Type contrat"
#define D_POWER_LOAD "Charge actuelle"
@ -843,5 +834,4 @@
#define D_OVERLOAD "ADPS"
#define D_MAX_POWER "Puissance max"
#define D_MAX_CURRENT "Courant max"
#endif // _LANGUAGE_FR_FR_H_

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -1,7 +1,7 @@
/*
it-IT.h - localization for Italian - Italy for Tasmota
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 19.08.2020
Copyright (C) 2020 Gennaro Tortone - some mods by Antonio Fragola - Updated by bovirus - rev. 05.09.2020
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -351,8 +351,8 @@
#define D_UPLOAD_STARTED "Caricamento..."
#define D_UPGRADE_STARTED "Aggiornamento..."
#define D_UPLOAD_DONE "Caricamento completato"
#define D_UPLOAD_TRANSFER "Upload transfer"
#define D_TRANSFER_STARTED "Transfer started"
#define D_UPLOAD_TRANSFER "Trasferimento caricamento"
#define D_TRANSFER_STARTED "Trasferimento avviato"
#define D_UPLOAD_ERR_1 "Nessun file selezionato"
#define D_UPLOAD_ERR_2 "Spazio insufficiente"
#define D_UPLOAD_ERR_3 "Magic byte non corrispondente a 0xE9"
@ -823,6 +823,8 @@
#define D_AS3935_NOISE "rilevato rumore"
#define D_AS3935_DISTDET "rilevato disturbatore"
#define D_AS3935_INTNOEV "Interrupt senza evento!"
#define D_AS3935_FLICKER "Flicker PIN IRQ!"
#define D_AS3935_POWEROFF "Spegnimento"
#define D_AS3935_NOMESS "in ascolto..."
#define D_AS3935_ON "ON"
#define D_AS3935_OFF "OFF"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "noise detected"
#define D_AS3935_DISTDET "disturber detected"
#define D_AS3935_INTNOEV "Interrupt with no Event!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "listening..."
#define D_AS3935_ON "On"
#define D_AS3935_OFF "Off"

View File

@ -823,6 +823,8 @@
#define D_AS3935_NOISE "偵測到雜訊"
#define D_AS3935_DISTDET "偵測到干擾物"
#define D_AS3935_INTNOEV "沒有任何事件觸發中斷!"
#define D_AS3935_FLICKER "IRQ flicker!"
#define D_AS3935_POWEROFF "Power Off"
#define D_AS3935_NOMESS "聽取中..."
#define D_AS3935_ON "開啟"
#define D_AS3935_OFF "關閉"

View File

@ -282,9 +282,9 @@ typedef union {
struct {
uint8_t nf_autotune : 1; // Autotune the NF Noise Level
uint8_t dist_autotune : 1; // Autotune Disturber on/off
uint8_t nf_autotune_both : 1; // Autotune over both Areas: INDOORS/OUDOORS
uint8_t nf_autotune_both : 1; // Autotune over both Areas: INDOORS/OUDOORS
uint8_t mqtt_only_Light_Event : 1; // mqtt only if lightning Irq
uint8_t spare4 : 1;
uint8_t suppress_irq_no_Event : 1; // suppress mqtt "IRQ with no Event". (Chip Bug)
uint8_t spare5 : 1;
uint8_t spare6 : 1;
uint8_t spare7 : 1;

View File

@ -323,7 +323,7 @@ void CmndBacklog(void)
backlog.add(blcommand);
}
#else
backlog[backlog_index] = String(blcommand);
backlog[backlog_index] = blcommand;
backlog_index++;
if (backlog_index >= MAX_BACKLOG) backlog_index = 0;
#endif

View File

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

View File

@ -0,0 +1,186 @@
/*
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
Copyright (C) 2020 Theo Arends and Stephan Hadinger
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*********************************************************************************************\
*
* private class for Linked List element
*
\*********************************************************************************************/
template <typename T>
class LList;
template <typename T>
class LList_elt {
public:
LList_elt() : _next(nullptr), _val() {}
inline T & val(void) { return _val; }
inline LList_elt<T> * next(void) { return _next; }
inline void next(LList_elt<T> * next) { _next = next; }
friend class LList<T>;
protected:
LList_elt<T> * _next;
T _val;
};
/*********************************************************************************************\
*
* Lightweight Linked List - optimized for low code size
*
\*********************************************************************************************/
template <typename T>
class LList {
public:
LList() : _head(nullptr) {}
~LList() { reset(); }
// remove elements
void removeHead(void); // remove first element
void reset(void); // remove all elements
void remove(const T * val);
// read the list
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
size_t length(void) const;
inline T * head(void) { return _head ? &_head->_val : nullptr; }
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
const T * at(size_t index) const ;
// non-const variants
// not very academic cast but reduces code size
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
// adding elements
T & addHead(void);
T & addHead(const T &val);
T & addToLast(void);
// iterator
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
class iterator {
public:
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const iterator & other) const { return cur != other.cur; }
T & operator*() const { return cur->_val; }
private:
LList_elt<T> *cur;
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
iterator begin() { return iterator(this->_head); } // start with 'head'
iterator end() { return iterator(nullptr); } // end with null pointer
// const iterator
class const_iterator {
public:
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
bool operator!=(const_iterator & other) const { return cur != other.cur; }
const T & operator*() const { return cur->_val; }
private:
const LList_elt<T> *cur;
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
};
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
protected:
LList_elt<T> * _head;
};
template <typename T>
size_t LList<T>::length(void) const {
size_t count = 0;
for (auto & elt : *this) {count++; }
return count;
}
template <typename T>
const T * LList<T>::at(size_t index) const {
size_t count = 0;
for (const auto & elt : *this) {
if (index == count++) { return &elt; }
}
return nullptr;
}
template <typename T>
void LList<T>::reset(void) {
while (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::removeHead(void) {
if (_head) {
LList_elt<T> * next = _head->next();
delete _head;
_head = next;
}
}
template <typename T>
void LList<T>::remove(const T * val) {
if (nullptr == val) { return; }
// find element in chain and find pointer before
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
LList_elt<T> * curr_elt = *curr_ptr;
if ( &(curr_elt->_val) == val) {
*curr_ptr = curr_elt->_next; // update previous pointer to next element
delete curr_elt;
break; // stop iteration now
}
curr_ptr = &((*curr_ptr)->_next); // move to next element
}
}
template <typename T>
T & LList<T>::addHead(void) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addHead(const T &val) {
LList_elt<T> * elt = new LList_elt<T>(); // create element
elt->next(_head); // insert at the head
elt->_val = val;
_head = elt;
return elt->_val;
}
template <typename T>
T & LList<T>::addToLast(void) {
LList_elt<T> **curr_ptr = &_head;
while (*curr_ptr) {
curr_ptr = &((*curr_ptr)->_next);
}
LList_elt<T> * elt = new LList_elt<T>(); // create element
*curr_ptr = elt;
return elt->_val;
}

View File

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

View File

@ -60,9 +60,9 @@
#ifdef USE_DISCOVERY
#include <ESP8266mDNS.h> // MQTT, Webserver, Arduino OTA
#endif // USE_DISCOVERY
#ifdef USE_I2C
//#ifdef USE_I2C
#include <Wire.h> // I2C support library
#endif // USE_I2C
//#endif // USE_I2C
#ifdef USE_SPI
#include <SPI.h> // SPI support, TFT
#endif // USE_SPI
@ -339,6 +339,7 @@ void BacklogLoop(void) {
#else
backlog_mutex = true;
ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG);
backlog[backlog_pointer] = (const char*) nullptr; // force deallocation of the String internal memory
backlog_pointer++;
if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; }
backlog_mutex = false;

View File

@ -228,7 +228,7 @@ enum UserSelectablePins {
GPIO_CC1101_GDO2, // CC1101 pin for RX
GPIO_HRXL_RX, // Data from MaxBotix HRXL sonar range sensor
GPIO_ELECTRIQ_MOODL_TX, // ElectriQ iQ-wifiMOODL Serial TX
GPIO_AS3935,
GPIO_AS3935, // Franklin Lightning Sensor
GPIO_PMS5003_TX, // Plantower PMS5003 Serial interface
GPIO_BOILER_OT_RX, // OpenTherm Boiler RX pin
GPIO_BOILER_OT_TX, // OpenTherm Boiler TX pin
@ -710,7 +710,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_DYP_RX,
#endif
#ifdef USE_AS3935
GPIO_AS3935,
GPIO_AS3935, // AS3935 IRQ Pin
#endif
#ifdef USE_TELEINFO
GPIO_TELEINFO_RX,

View File

@ -111,7 +111,7 @@ enum UserSelectablePins {
GPIO_CC1101_GDO0, GPIO_CC1101_GDO2, // CC1101 Serial interface
GPIO_HRXL_RX, // Data from MaxBotix HRXL sonar range sensor
GPIO_ELECTRIQ_MOODL_TX, // ElectriQ iQ-wifiMOODL Serial TX
GPIO_AS3935,
GPIO_AS3935, // Franklin Lightning Sensor
GPIO_ADC_INPUT, // Analog input
GPIO_ADC_TEMP, // Analog Thermistor
GPIO_ADC_LIGHT, // Analog Light sensor
@ -557,7 +557,7 @@ const uint16_t kGpioNiceList[] PROGMEM = {
AGPIO(GPIO_DYP_RX),
#endif
#ifdef USE_AS3935
AGPIO(GPIO_AS3935),
AGPIO(GPIO_AS3935), // AS3935 IRQ Pin
#endif
#ifdef USE_TELEINFO
AGPIO(GPIO_TELEINFO_RX),

View File

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

View File

@ -2712,6 +2712,7 @@ void HandleUploadLoop(void)
HTTPUpload& upload = Webserver->upload();
// ***** Step1: Start upload file
if (UPLOAD_FILE_START == upload.status) {
restart_flag = 60;
if (0 == upload.filename.c_str()[0]) {
@ -2752,7 +2753,10 @@ void HandleUploadLoop(void)
}
}
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 (UPL_SETTINGS == Web.upload_file_type) {
Web.config_block_count = 0;
@ -2781,9 +2785,10 @@ void HandleUploadLoop(void)
} else
#endif // USE_RF_FLASH
#ifdef USE_TASMOTA_CLIENT
if ((WEMOS == my_module_type) && (upload.buf[0] == ':')) { // Check if this is a ARDUINO CLIENT hex file
if (TasmotaClient_Available() && (upload.buf[0] == ':')) { // Check if this is a ARDUINO CLIENT hex file
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_TASMOTACLIENT;
Web.upload_error = TasmotaClient_UpdateInit(); // 0
if (Web.upload_error != 0) { return; }
} else
@ -2802,6 +2807,7 @@ void HandleUploadLoop(void)
// 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) {
@ -2873,7 +2879,10 @@ void HandleUploadLoop(void)
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)) {
Serial.println();
}
@ -2949,9 +2958,12 @@ void HandleUploadLoop(void)
}
}
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;
MqttRetryCounter(0);
#ifdef USE_COUNTER

View File

@ -300,12 +300,35 @@ struct T_INDEX {
};
struct M_FILT {
#ifdef LARGE_ARRAYS
uint16_t numvals;
uint16_t index;
#else
uint8_t numvals;
uint8_t index;
#endif // LARGE_ARRAYS
float maccu;
float rbuff[1];
};
#ifdef LARGE_ARRAYS
#undef AND_FILT_MASK
#undef OR_FILT_MASK
#define AND_FILT_MASK 0x7fff
#define OR_FILT_MASK 0x8000
#undef MAX_ARRAY_SIZE
#define MAX_ARRAY_SIZE 1000
#else
#undef AND_FILT_MASK
#undef OR_FILT_MASK
#define AND_FILT_MASK 0x7f
#define OR_FILT_MASK 0x80
#undef MAX_ARRAY_SIZE
#define MAX_ARRAY_SIZE 127
#endif
typedef union {
uint8_t data;
struct {
@ -462,6 +485,8 @@ void RulesTeleperiod(void) {
#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++;
#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++;
float *Get_MFAddr(uint8_t index,uint16_t *len,uint16_t *ipos);
// allocates all variables and presets them
int16_t Init_Scripter(void) {
char *script;
@ -543,12 +568,16 @@ char *script;
if ((*lp=='m' || *lp=='M') && *(lp+1)==':') {
uint8_t flg=*lp;
lp+=2;
if (*lp=='p' && *(lp+1)==':') {
vtypes[vars].bits.is_permanent=1;
lp+=2;
}
if (flg=='M') mfilt[numflt].numvals=8;
else mfilt[numflt].numvals=5;
vtypes[vars].bits.is_filter=1;
mfilt[numflt].index=0;
if (flg=='M') {
mfilt[numflt].numvals|=0x80;
mfilt[numflt].numvals|=OR_FILT_MASK;
}
vtypes[vars].index=numflt;
numflt++;
@ -587,9 +616,13 @@ char *script;
while (*op==' ') op++;
if (isdigit(*op)) {
// lenght define follows
uint8_t flen=atoi(op);
mfilt[numflt-1].numvals&=0x80;
mfilt[numflt-1].numvals|=flen&0x7f;
uint16_t flen=atoi(op);
if (flen>MAX_ARRAY_SIZE) {
// limit array size
flen=MAX_ARRAY_SIZE;
}
mfilt[numflt-1].numvals&=OR_FILT_MASK;
mfilt[numflt-1].numvals|=flen&AND_FILT_MASK;
}
}
@ -635,11 +668,11 @@ char *script;
uint16_t fsize=0;
for (count=0; count<numflt; count++) {
fsize+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
fsize+=sizeof(struct M_FILT)+((mfilt[count].numvals&AND_FILT_MASK)-1)*sizeof(float);
}
// now copy vars to memory
uint16_t script_mem_size=
uint32_t script_mem_size=
// number and number shadow vars
(sizeof(float)*nvars)+
(sizeof(float)*nvars)+
@ -733,7 +766,7 @@ char *script;
for (count=0; count<numflt; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
mflp->numvals=mfilt[count].numvals;
mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&AND_FILT_MASK)-1)*sizeof(float);
}
glob_script_mem.numvars=vars;
@ -760,12 +793,21 @@ char *script;
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
if (!isnan(*fp)) {
glob_script_mem.fvars[index]=*fp;
if (vtp[count].bits.is_filter) {
// preset array
uint16_t len=0;
float *fa=Get_MFAddr(index,&len,0);
while (len--) {
*fa++=*fp++;
}
} else {
*fp=glob_script_mem.fvars[index];
if (!isnan(*fp)) {
glob_script_mem.fvars[index]=*fp;
} else {
*fp=glob_script_mem.fvars[index];
}
fp++;
}
fp++;
}
}
sp=(char*)fp;
@ -947,7 +989,7 @@ void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) {
float median_array(float *array,uint8_t len) {
float median_array(float *array,uint16_t len) {
uint8_t ind[len];
uint8_t mind=0,index=0,flg;
float min=FLT_MAX;
@ -975,45 +1017,48 @@ float median_array(float *array,uint8_t len) {
}
float *Get_MFAddr(uint8_t index,uint8_t *len,uint8_t *ipos) {
float *Get_MFAddr(uint8_t index,uint16_t *len,uint16_t *ipos) {
*len=0;
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
*len=mflp->numvals&0x7f;
*len=mflp->numvals&AND_FILT_MASK;
if (ipos) *ipos=mflp->index;
return mflp->rbuff;
}
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mflp->numvals&AND_FILT_MASK)-1)*sizeof(float);
}
return 0;
}
float Get_MFVal(uint8_t index,uint8_t bind) {
float Get_MFVal(uint8_t index,int16_t bind) {
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
uint8_t maxind=mflp->numvals&0x7f;
uint16_t maxind=mflp->numvals&AND_FILT_MASK;
if (!bind) {
return mflp->index;
}
if (bind<0) {
return maxind;
}
if (bind<1 || bind>maxind) bind=maxind;
return mflp->rbuff[bind-1];
}
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mflp->numvals&AND_FILT_MASK)-1)*sizeof(float);
}
return 0;
}
void Set_MFVal(uint8_t index,uint8_t bind,float val) {
void Set_MFVal(uint8_t index,uint16_t bind,float val) {
uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
uint8_t maxind=mflp->numvals&0x7f;
uint16_t maxind=mflp->numvals&AND_FILT_MASK;
if (!bind) {
mflp->index=val;
} else {
@ -1022,7 +1067,7 @@ void Set_MFVal(uint8_t index,uint8_t bind,float val) {
}
return;
}
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mflp->numvals&AND_FILT_MASK)-1)*sizeof(float);
}
}
@ -1032,15 +1077,15 @@ float Get_MFilter(uint8_t index) {
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals&0x80) {
if (mflp->numvals&OR_FILT_MASK) {
// moving average
return mflp->maccu/(mflp->numvals&0x7f);
return mflp->maccu/(mflp->numvals&AND_FILT_MASK);
} else {
// median, sort array indices
return median_array(mflp->rbuff,mflp->numvals);
}
}
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mflp->numvals&AND_FILT_MASK)-1)*sizeof(float);
}
return 0;
}
@ -1050,13 +1095,13 @@ void Set_MFilter(uint8_t index, float invar) {
for (uint8_t count=0; count<MAXFILT; count++) {
struct M_FILT *mflp=(struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals&0x80) {
if (mflp->numvals&OR_FILT_MASK) {
// moving average
mflp->maccu-=mflp->rbuff[mflp->index];
mflp->maccu+=invar;
mflp->rbuff[mflp->index]=invar;
mflp->index++;
if (mflp->index>=(mflp->numvals&0x7f)) mflp->index=0;
if (mflp->index>=(mflp->numvals&AND_FILT_MASK)) mflp->index=0;
} else {
// median
mflp->rbuff[mflp->index]=invar;
@ -1065,7 +1110,7 @@ void Set_MFilter(uint8_t index, float invar) {
}
break;
}
mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
mp+=sizeof(struct M_FILT)+((mflp->numvals&AND_FILT_MASK)-1)*sizeof(float);
}
}
@ -2140,7 +2185,7 @@ chknext:
}
} else {
if (index>glob_script_mem.si_num) {
fvar=glob_script_mem.si_num;
index=glob_script_mem.si_num;
}
strlcpy(str,glob_script_mem.last_index_string+(index*glob_script_mem.max_ssize),glob_script_mem.max_ssize);
}
@ -2691,7 +2736,7 @@ chknext:
if (index<1 || index>MAXBUTTONS) index=1;
index--;
if (buttons[index]) {
fvar=buttons[index]->vpower&0x80;
fvar=buttons[index]->vpower.on_off;
} else {
fvar=-1;
}
@ -2807,7 +2852,7 @@ chknext:
#if defined(USE_TTGO_WATCH) && defined(USE_FT5206)
if (!strncmp(vname,"wtch(",5)) {
lp=GetNumericResult(lp+5,OPER_EQU,&fvar,0);
fvar=FT5206_touched(fvar);
fvar=Touch_Status(fvar);
lp++;
len=0;
goto exit;
@ -3430,7 +3475,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
int16_t Run_script_sub(const char *type, int8_t tlen, JsonObject *jo) {
uint8_t vtype=0,sindex,xflg,floop=0,globvindex,fromscriptcmd=0;
char *lp_next;
int8_t globaindex,saindex;
int16_t globaindex,saindex;
struct T_INDEX ind;
uint8_t operand,lastop,numeric=1,if_state[IF_NEST],if_exe[IF_NEST],if_result[IF_NEST],and_or,ifstck=0;
if_state[ifstck]=0;
@ -3762,7 +3807,7 @@ int16_t Run_script_sub(const char *type, int8_t tlen, JsonObject *jo) {
if ((vtype&STYPE)==0) {
// numeric result
if (glob_script_mem.type[ind.index].bits.is_filter) {
uint8_t len=0;
uint16_t len=0;
float *fa=Get_MFAddr(index,&len,0);
//Serial.printf(">> 2 %d\n",(uint32_t)*fa);
if (fa && len) ws2812_set_array(fa,len,fvar);
@ -4175,12 +4220,26 @@ void Scripter_save_pvars(void) {
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
mlen+=sizeof(float);
if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
if (vtp[count].bits.is_filter) {
// save array
uint16_t len=0;
float *fa=Get_MFAddr(index,&len,0);
mlen+=sizeof(float)*len;
if (mlen>glob_script_mem.script_pram_size) {
vtp[count].bits.is_permanent=0;
return;
}
while (len--) {
*fp++=*fa++;
}
} else {
mlen+=sizeof(float);
if (mlen>glob_script_mem.script_pram_size) {
vtp[count].bits.is_permanent=0;
return;
}
*fp++=glob_script_mem.fvars[index];
}
*fp++=glob_script_mem.fvars[index];
}
}
char *cp=(char*)fp;
@ -4190,7 +4249,7 @@ void Scripter_save_pvars(void) {
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp);
mlen+=slen+1;
if (mlen>PMEM_SIZE) {
if (mlen>glob_script_mem.script_pram_size) {
vtp[count].bits.is_permanent=0;
return;
}
@ -6013,6 +6072,9 @@ const char SCRIPT_MSG_GOPT4[] PROGMEM =
const char SCRIPT_MSG_GOPT5[] PROGMEM =
"new Date(0,1,1,%d,%d)";
const char SCRIPT_MSG_GOPT6[] PROGMEM =
"title:'%s',isStacked:false,vAxis:{viewWindow:{min:%d,max:%d}}%s";
const char SCRIPT_MSG_GTE1[] PROGMEM = "'%s'";
#define GLIBS_MAIN 1<<0
@ -6022,11 +6084,11 @@ const char SCRIPT_MSG_GTE1[] PROGMEM = "'%s'";
#define MAX_GARRAY 4
char *gc_get_arrays(char *lp, float **arrays, uint8_t *ranum, uint8_t *rentries, uint8_t *ipos) {
char *gc_get_arrays(char *lp, float **arrays, uint8_t *ranum, uint16_t *rentries, uint16_t *ipos) {
struct T_INDEX ind;
uint8_t vtype;
uint8 entries=0;
uint8_t cipos=0;
uint16 entries=0;
uint16_t cipos=0;
uint8_t anum=0;
while (anum<MAX_GARRAY) {
@ -6042,9 +6104,9 @@ uint8_t cipos=0;
//Serial.printf("numeric %d - %d \n",ind.index,index);
if (glob_script_mem.type[ind.index].bits.is_filter) {
//Serial.printf("numeric array\n");
uint8_t len=0;
uint16_t len=0;
float *fa=Get_MFAddr(index,&len,&cipos);
//Serial.printf(">> 2 %d\n",(uint32_t)*fa);
//Serial.printf(">> 2 %d\n",len);
if (fa && len>=entries) {
if (!entries) {
entries = len;
@ -6295,12 +6357,12 @@ void ScriptWebShow(char mc) {
} else {
if (mc=='w') {
WSContentSend_PD(PSTR("%s"),tmp);
WSContentSend_PD(PSTR("%s"),lin);
} else {
if (optflg) {
WSContentSend_PD(PSTR("<div>%s</div>"),tmp);
WSContentSend_PD(PSTR("<div>%s</div>"),lin);
} else {
WSContentSend_PD(PSTR("{s}%s{e}"),tmp);
WSContentSend_PD(PSTR("{s}%s{e}"),lin);
}
}
}
@ -6388,8 +6450,8 @@ exgc:
float *arrays[MAX_GARRAY];
uint8_t anum=0;
uint8 entries=0;
uint8 ipos=0;
uint16_t entries=0;
uint16_t ipos=0;
lp=gc_get_arrays(lp, &arrays[0], &anum, &entries, &ipos);
if (anum>nanum) {
@ -6434,10 +6496,19 @@ exgc:
lp=GetStringResult(lp,OPER_EQU,label,0);
SCRIPT_SKIP_SPACES
int8_t todflg=-1;
int16_t divflg=1;
int16_t todflg=-1;
if (!strncmp(label,"cnt",3)) {
todflg=atoi(&label[3]);
if (todflg>=entries) todflg=entries-1;
} else {
uint16 segments=1;
for (uint32_t cnt=0; cnt<strlen(label); cnt++) {
if (label[cnt]=='|') {
segments++;
}
}
divflg=entries/segments;
}
uint32_t aind=ipos;
@ -6452,7 +6523,7 @@ exgc:
todflg=0;
}
} else {
GetTextIndexed(lbl, sizeof(lbl), aind, label);
GetTextIndexed(lbl, sizeof(lbl), aind/divflg, label);
}
WSContentSend_PD(lbl);
WSContentSend_PD("',");
@ -6495,6 +6566,17 @@ exgc:
lp=GetNumericResult(lp,OPER_EQU,&max2,0);
SCRIPT_SKIP_SPACES
snprintf_P(options,sizeof(options),SCRIPT_MSG_GOPT3,header,(uint32_t)max1,(uint32_t)max2,func);
} else {
SCRIPT_SKIP_SPACES
if (*lp!=')') {
float max1;
lp=GetNumericResult(lp,OPER_EQU,&max1,0);
SCRIPT_SKIP_SPACES
float max2;
lp=GetNumericResult(lp,OPER_EQU,&max2,0);
SCRIPT_SKIP_SPACES
snprintf_P(options,sizeof(options),SCRIPT_MSG_GOPT6,header,(uint32_t)max1,(uint32_t)max2,func);
}
}
if (ctype=='g') {

View File

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

View File

@ -23,7 +23,6 @@
#define XDRV_13 13
#include <renderer.h>
#include <FT6236.h>
Renderer *renderer;
@ -757,7 +756,28 @@ void DisplayText(void)
#ifdef USE_TOUCH_BUTTONS
case 'b':
{ int16_t num,gxp,gyp,gxs,gys,outline,fill,textcolor,textsize;
{ int16_t num,gxp,gyp,gxs,gys,outline,fill,textcolor,textsize; uint8_t dflg=1;
if (*cp=='e' || *cp=='d') {
// enable disable
uint8_t dis=0;
if (*cp=='d') dis=1;
cp++;
var=atoiv(cp,&num);
num=num%MAXBUTTONS;
cp+=var;
if (buttons[num]) {
buttons[num]->vpower.disable=dis;
if (!dis) {
if (buttons[num]->vpower.is_virtual) buttons[num]->xdrawButton(buttons[num]->vpower.on_off);
else buttons[num]->xdrawButton(bitRead(power,num));
}
}
break;
}
if (*cp=='-') {
cp++;
dflg=0;
}
var=atoiv(cp,&num);
cp+=var;
cp++;
@ -797,16 +817,23 @@ void DisplayText(void)
if (renderer) {
buttons[num]= new VButton();
if (buttons[num]) {
buttons[num]->vpower=bflags;
buttons[num]->initButtonUL(renderer,gxp,gyp,gxs,gys,renderer->GetColorFromIndex(outline),\
renderer->GetColorFromIndex(fill),renderer->GetColorFromIndex(textcolor),bbuff,textsize);
renderer->GetColorFromIndex(fill),renderer->GetColorFromIndex(textcolor),bbuff,textsize);
if (!bflags) {
// power button
buttons[num]->xdrawButton(bitRead(power,num));
if (dflg) buttons[num]->xdrawButton(bitRead(power,num));
buttons[num]->vpower.is_virtual=0;
} else {
// virtual button
buttons[num]->vpower&=0x7f;
buttons[num]->xdrawButton(buttons[num]->vpower&0x80);
buttons[num]->vpower.is_virtual=1;
if (bflags==2) {
// push
buttons[num]->vpower.is_pushbutton=1;
} else {
// toggle
buttons[num]->vpower.is_pushbutton=0;
}
if (dflg) buttons[num]->xdrawButton(buttons[num]->vpower.on_off);
}
}
}
@ -2019,7 +2046,146 @@ void AddValue(uint8_t num,float fval) {
}
}
}
#endif
#endif // USE_GRAPH
#ifdef USE_FT5206
// touch panel controller
#undef FT5206_address
#define FT5206_address 0x38
#include <FT5206.h>
FT5206_Class *touchp;
TP_Point pLoc;
extern VButton *buttons[];
bool FT5206_found;
bool Touch_Init(TwoWire &i2c) {
FT5206_found = false;
touchp = new FT5206_Class();
if (touchp->begin(i2c, FT5206_address)) {
I2cSetActiveFound(FT5206_address, "FT5206");
FT5206_found = true;
}
return FT5206_found;
}
uint32_t Touch_Status(uint32_t sel) {
if (FT5206_found) {
switch (sel) {
case 0:
return touchp->touched();
case 1:
return pLoc.x;
case 2:
return pLoc.y;
}
return 0;
} else {
return 0;
}
}
#ifdef USE_TOUCH_BUTTONS
void Touch_MQTT(uint8_t index, const char *cp) {
ResponseTime_P(PSTR(",\"FT5206\":{\"%s%d\":\"%d\"}}"), cp, index+1, buttons[index]->vpower.on_off);
MqttPublishTeleSensor();
}
void Touch_RDW_BUTT(uint32_t count, uint32_t pwr) {
buttons[count]->xdrawButton(pwr);
if (pwr) buttons[count]->vpower.on_off = 1;
else buttons[count]->vpower.on_off = 0;
}
// check digitizer hit
void Touch_Check(void(*rotconvert)(int16_t *x, int16_t *y)) {
uint16_t temp;
uint8_t rbutt=0;
uint8_t vbutt=0;
if (touchp->touched()) {
// did find a hit
pLoc = touchp->getPoint(0);
if (renderer) {
rotconvert(&pLoc.x, &pLoc.y);
//AddLog_P2(LOG_LEVEL_INFO, PSTR("touch %d - %d"), pLoc.x, pLoc.y);
// now must compare with defined buttons
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count] && !buttons[count]->vpower.disable) {
if (buttons[count]->contains(pLoc.x, pLoc.y)) {
// did hit
buttons[count]->press(true);
if (buttons[count]->justPressed()) {
if (!buttons[count]->vpower.is_virtual) {
uint8_t pwr=bitRead(power, rbutt);
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
Touch_RDW_BUTT(count, !pwr);
}
} else {
// virtual button
const char *cp;
if (!buttons[count]->vpower.is_pushbutton) {
// toggle button
buttons[count]->vpower.on_off ^= 1;
cp="TBT";
} else {
// push button
buttons[count]->vpower.on_off = 1;
cp="PBT";
}
buttons[count]->xdrawButton(buttons[count]->vpower.on_off);
Touch_MQTT(count,cp);
}
}
}
if (!buttons[count]->vpower.is_virtual) {
rbutt++;
} else {
vbutt++;
}
}
}
}
} else {
// no hit
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
buttons[count]->press(false);
if (buttons[count]->justReleased()) {
if (buttons[count]->vpower.is_virtual) {
if (buttons[count]->vpower.is_pushbutton) {
// push button
buttons[count]->vpower.on_off = 0;
Touch_MQTT(count,"PBT");
buttons[count]->xdrawButton(buttons[count]->vpower.on_off);
}
}
}
if (!buttons[count]->vpower.is_virtual) {
// check if power button stage changed
uint8_t pwr = bitRead(power, rbutt);
uint8_t vpwr = buttons[count]->vpower.on_off;
if (pwr != vpwr) {
Touch_RDW_BUTT(count, pwr);
}
rbutt++;
}
}
}
pLoc.x = 0;
pLoc.y = 0;
}
}
#endif // USE_TOUCH_BUTTONS
#endif // USE_FT5206
/*********************************************************************************************\
* Interface

View File

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

View File

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

View File

@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
#include <vector>
#ifndef ZIGBEE_SAVE_DELAY_SECONDS
#define ZIGBEE_SAVE_DELAY_SECONDS 2 // wait for 2s before saving Zigbee info
#endif
@ -29,25 +27,6 @@ const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait f
/*********************************************************************************************\
* Structures for Rules variables related to the last received message
\*********************************************************************************************/
typedef struct Z_LastMessageVars {
uint16_t device; // device short address
uint16_t groupaddr; // group address
uint16_t cluster; // cluster id
uint8_t endpoint; // source endpoint
} Z_LastMessageVars;
Z_LastMessageVars gZbLastMessage;
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
/*********************************************************************************************\
* Structures for device configuration
\*********************************************************************************************/
const size_t endpoints_max = 8; // we limit to 8 endpoints
class Z_Device {
@ -58,9 +37,8 @@ public:
char * modelId;
char * friendlyName;
uint8_t endpoints[endpoints_max]; // static array to limit memory consumption, list of endpoints until 0x00 or end of array
// json buffer used for attribute reporting
DynamicJsonBuffer *json_buffer;
JsonObject *json;
// Used for attribute reporting
Z_attribute_list attr_list;
// sequence number for Zigbee frames
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint8_t seqNumber;
@ -95,14 +73,13 @@ public:
int16_t mains_power; // Active power
// Constructor with all defaults
Z_Device(uint16_t _shortaddr, uint64_t _longaddr = 0x00):
Z_Device(uint16_t _shortaddr = BAD_SHORTADDR, uint64_t _longaddr = 0x00):
longaddr(_longaddr),
manufacturerId(nullptr),
modelId(nullptr),
friendlyName(nullptr),
endpoints{ 0, 0, 0, 0, 0, 0, 0, 0 },
json_buffer(nullptr),
json(nullptr),
attr_list(),
shortaddr(_shortaddr),
seqNumber(0),
// Hue support
@ -207,7 +184,7 @@ typedef struct Z_Deferred {
// - shortaddr and longaddr cannot be both null
class Z_Devices {
public:
Z_Devices() {};
Z_Devices() : _deferred() {};
// Probe the existence of device keys
// Results:
@ -218,12 +195,17 @@ public:
uint16_t isKnownIndex(uint32_t index) const;
uint16_t isKnownFriendlyName(const char * name) const;
Z_Device & findShortAddr(uint16_t shortaddr);
const Z_Device & findShortAddr(uint16_t shortaddr) const;
Z_Device & findLongAddr(uint64_t longaddr);
const Z_Device & findLongAddr(uint64_t longaddr) const;
Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist
const Z_Device & getShortAddrConst(uint16_t shortaddr) const ; // find Device from shortAddr, creates it if does not exist
Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist
// check if a device was found or if it's the fallback device
inline bool foundDevice(const Z_Device & device) const {
return (&device != &device_unk);
}
int32_t findLongAddr(uint64_t longaddr) const;
int32_t findFriendlyName(const char * name) const;
uint64_t getDeviceLongAddr(uint16_t shortaddr) const;
@ -281,19 +263,22 @@ public:
void runTimer(void);
// Append or clear attributes Json structure
void jsonClear(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const JsonObject &values);
const JsonObject *jsonGet(uint16_t shortaddr);
void jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list);
void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer
bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
void jsonPublishNow(uint16_t shortaddr, JsonObject &values);
bool jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const;
void jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list);
// Iterator
size_t devicesSize(void) const {
return _devices.size();
return _devices.length();
}
const Z_Device &devicesAt(size_t i) const {
return *(_devices.at(i));
const Z_Device & devicesAt(size_t i) const {
const Z_Device * devp = _devices.at(i);
if (devp) {
return *devp;
} else {
return device_unk;
}
}
// Remove device from list
@ -308,8 +293,8 @@ public:
uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const;
private:
std::vector<Z_Device*> _devices = {};
std::vector<Z_Deferred> _deferred = {}; // list of deferred calls
LList<Z_Device> _devices; // list of devices
LList<Z_Deferred> _deferred; // list of deferred calls
uint32_t _saveTimer = 0;
uint8_t _seqNumber = 0; // global seqNumber if device is unknown
@ -317,10 +302,7 @@ private:
// Any find() function will not return Null, instead it will return this instance
const Z_Device device_unk = Z_Device(BAD_SHORTADDR);
template < typename T>
static bool findInVector(const std::vector<T> & vecOfElements, const T & element);
int32_t findShortAddrIdx(uint16_t shortaddr) const;
//int32_t findShortAddrIdx(uint16_t shortaddr) const;
// Create a new entry in the devices list - must be called if it is sure it does not already exist
Z_Device & createDeviceEntry(uint16_t shortaddr, uint64_t longaddr = 0);
void freeDeviceEntry(Z_Device *device);
@ -343,31 +325,16 @@ uint16_t localShortAddr = 0;
* Implementation
\*********************************************************************************************/
// https://thispointer.com/c-how-to-find-an-element-in-vector-and-get-its-index/
template < typename T>
bool Z_Devices::findInVector(const std::vector<T> & vecOfElements, const T & element) {
// Find given element in vector
auto it = std::find(vecOfElements.begin(), vecOfElements.end(), element);
if (it != vecOfElements.end()) {
return true;
} else {
return false;
}
}
//
// Create a new Z_Device entry in _devices. Only to be called if you are sure that no
// entry with same shortaddr or longaddr exists.
//
Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return (Z_Device&) device_unk; } // it is not legal to create this entry
Z_Device * device_alloc = new Z_Device(shortaddr, longaddr);
Z_Device device(shortaddr, longaddr);
device_alloc->json_buffer = new DynamicJsonBuffer(16);
_devices.push_back(device_alloc);
dirty();
return *(_devices.back());
return _devices.addHead(device);
}
void Z_Devices::freeDeviceEntry(Z_Device *device) {
@ -383,20 +350,17 @@ void Z_Devices::freeDeviceEntry(Z_Device *device) {
// In:
// shortaddr (not BAD_SHORTADDR)
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findShortAddrIdx(uint16_t shortaddr) const {
if (BAD_SHORTADDR == shortaddr) { return -1; } // does not make sense to look for BAD_SHORTADDR shortaddr (broadcast)
int32_t found = 0;
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return found; }
found++;
// reference to device, or to device_unk if not found
// (use foundDevice() to check if found)
Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) {
for (auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
for (auto &elem : _devices) {
if (elem->shortaddr == shortaddr) { return *elem; }
for (const auto & elem : _devices) {
if (elem.shortaddr == shortaddr) { return elem; }
}
return device_unk;
}
@ -408,14 +372,19 @@ const Z_Device & Z_Devices::findShortAddr(uint16_t shortaddr) const {
// Out:
// index in _devices of entry, -1 if not found
//
int32_t Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return -1; }
int32_t found = 0;
Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
for (auto &elem : _devices) {
if (elem->longaddr == longaddr) { return found; }
found++;
if (elem.longaddr == longaddr) { return elem; }
}
return -1;
return (Z_Device&) device_unk;
}
const Z_Device & Z_Devices::findLongAddr(uint64_t longaddr) const {
if (!longaddr) { return device_unk; }
for (const auto &elem : _devices) {
if (elem.longaddr == longaddr) { return elem; }
}
return device_unk;
}
//
// Scan all devices to find a corresponding friendlyNme
@ -431,8 +400,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
int32_t found = 0;
if (name_len) {
for (auto &elem : _devices) {
if (elem->friendlyName) {
if (strcasecmp(elem->friendlyName, name) == 0) { return found; }
if (elem.friendlyName) {
if (strcasecmp(elem.friendlyName, name) == 0) { return found; }
}
found++;
}
@ -441,9 +410,8 @@ int32_t Z_Devices::findFriendlyName(const char * name) const {
}
uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const {
int32_t found = findLongAddr(longaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device.shortaddr; // can be zero, if not yet registered
} else {
return BAD_SHORTADDR;
@ -471,7 +439,7 @@ uint16_t Z_Devices::isKnownFriendlyName(const char * name) const {
}
uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
return getShortAddrConst(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
return findShortAddr(shortaddr).longaddr; // if unknown, it reverts to the Unknown device and longaddr is 0x00
}
//
@ -479,38 +447,28 @@ uint64_t Z_Devices::getDeviceLongAddr(uint16_t shortaddr) const {
//
Z_Device & Z_Devices::getShortAddr(uint16_t shortaddr) {
if (BAD_SHORTADDR == shortaddr) { return (Z_Device&) device_unk; } // this is not legal
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
return device;
}
//Serial.printf("Device entry created for shortaddr = 0x%02X, found = %d\n", shortaddr, found);
return createDeviceEntry(shortaddr, 0);
}
// Same version but Const
const Z_Device & Z_Devices::getShortAddrConst(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
return *(_devices[found]);
}
return device_unk;
}
// find the Device object by its longaddr (unique key if not null)
Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) {
if (!longaddr) { return (Z_Device&) device_unk; }
int32_t found = findLongAddr(longaddr);
if (found > 0) {
return *(_devices[found]);
Z_Device & device = findLongAddr(longaddr);
if (foundDevice(device)) {
return device;
}
return createDeviceEntry(0, longaddr);
}
// Remove device from list, return true if it was known, false if it was not recorded
bool Z_Devices::removeDevice(uint16_t shortaddr) {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
freeDeviceEntry(_devices.at(found));
_devices.erase(_devices.begin() + found);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
_devices.remove(&device);
dirty();
return true;
}
@ -523,27 +481,27 @@ bool Z_Devices::removeDevice(uint16_t shortaddr) {
// shortaddr
// longaddr (both can't be null at the same time)
void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) {
int32_t s_found = findShortAddrIdx(shortaddr); // is there already a shortaddr entry
int32_t l_found = findLongAddr(longaddr); // is there already a longaddr entry
Z_Device * s_found = &findShortAddr(shortaddr); // is there already a shortaddr entry
Z_Device * l_found = &findLongAddr(longaddr); // is there already a longaddr entry
if ((s_found >= 0) && (l_found >= 0)) { // both shortaddr and longaddr are already registered
if (foundDevice(*s_found) && foundDevice(*l_found)) { // both shortaddr and longaddr are already registered
if (s_found == l_found) {
} else { // they don't match
// the device with longaddr got a new shortaddr
_devices[l_found]->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
l_found->shortaddr = shortaddr; // update the shortaddr corresponding to the longaddr
// erase the previous shortaddr
freeDeviceEntry(_devices.at(s_found));
_devices.erase(_devices.begin() + s_found);
freeDeviceEntry(s_found);
_devices.remove(s_found);
dirty();
}
} else if (s_found >= 0) {
} else if (foundDevice(*s_found)) {
// shortaddr already exists but longaddr not
// add the longaddr to the entry
_devices[s_found]->longaddr = longaddr;
s_found->longaddr = longaddr;
dirty();
} else if (l_found >= 0) {
} else if (foundDevice(*l_found)) {
// longaddr entry exists, update shortaddr
_devices[l_found]->shortaddr = shortaddr;
l_found->shortaddr = shortaddr;
dirty();
} else {
// neither short/lonf addr are found.
@ -588,9 +546,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) {
//
uint32_t Z_Devices::countEndpoints(uint16_t shortaddr) const {
uint32_t count_ep = 0;
int32_t found = findShortAddrIdx(shortaddr);
if (found < 0) return 0; // avoid creating an entry if the device was never seen
const Z_Device &device = devicesAt(found);
const Z_Device & device =findShortAddr(shortaddr);
if (!foundDevice(device)) return 0;
for (uint32_t i = 0; i < endpoints_max; i++) {
if (0 != device.endpoints[i]) {
@ -666,9 +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
uint8_t Z_Devices::getNextSeqNumber(uint16_t shortaddr) {
int32_t short_found = findShortAddrIdx(shortaddr);
if (short_found >= 0) {
Z_Device &device = getShortAddr(shortaddr);
Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
device.seqNumber += 1;
return device.seqNumber;
} else {
@ -754,9 +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
bool Z_Devices::isHueBulbHidden(uint16_t shortaddr) const {
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
uint8_t zb_profile = _devices[found]->zb_profile;
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
uint8_t zb_profile = device.zb_profile;
if (0x00 == (zb_profile & 0xF0)) {
// bulb type
return (zb_profile & 0x08) ? true : false;
@ -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
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
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
// Notice that the iterator is decremented after it is passed
// to erase() but before erase() is executed
// see https://www.techiedelight.com/remove-elements-vector-inside-loop-cpp/
if ((it->shortaddr == shortaddr) && (it->groupaddr == groupaddr)) {
if ((0xFF == category) || (it->category == category)) {
_deferred.erase(it--);
for (auto & defer : _deferred) {
if ((defer.shortaddr == shortaddr) && (defer.groupaddr == groupaddr)) {
if ((0xFF == category) || (defer.category == category)) {
_deferred.remove(&defer);
}
}
}
@ -789,7 +742,8 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
}
// Now create the new timer
Z_Deferred deferred = { wait_ms + millis(), // timer
Z_Deferred & deferred = _deferred.addHead();
deferred = { wait_ms + millis(), // timer
shortaddr,
groupaddr,
cluster,
@ -797,20 +751,17 @@ void Z_Devices::setTimer(uint16_t shortaddr, uint16_t groupaddr, uint32_t wait_m
category,
value,
func };
_deferred.push_back(deferred);
}
// Run timer at each tick
// WARNING: don't set a new timer within a running timer, this causes memory corruption
void Z_Devices::runTimer(void) {
// visit all timers
for (auto it = _deferred.begin(); it != _deferred.end(); it++) {
Z_Deferred &defer = *it;
for (auto & defer : _deferred) {
uint32_t timer = defer.timer;
if (TimeReached(timer)) {
(*defer.func)(defer.shortaddr, defer.groupaddr, defer.cluster, defer.endpoint, defer.value);
_deferred.erase(it--); // remove from list
_deferred.remove(&defer);
}
}
@ -821,173 +772,100 @@ void Z_Devices::runTimer(void) {
}
}
// Clear the JSON buffer for coalesced and deferred attributes
void Z_Devices::jsonClear(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
device.json = nullptr;
device.json_buffer->clear();
}
// Copy JSON from one object to another, this helps preserving the order of attributes
void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
// first remove the potentially existing key in the target JSON, so new adds will be at the end of the list
to.remove(key); // force remove to have metadata like LinkQuality at the end
if (val.is<char*>()) {
const char * sval = val.as<char*>(); // using char* forces a copy, and also captures 'null' values
to.set(key, (char*) sval);
} else if (val.is<JsonArray>()) {
JsonArray &nested_arr = to.createNestedArray(key);
CopyJsonArray(nested_arr, val.as<JsonArray>()); // deep copy
} else if (val.is<JsonObject>()) {
JsonObject &nested_obj = to.createNestedObject(key);
CopyJsonObject(nested_obj, val.as<JsonObject>()); // deep copy
} else {
to.set(key, val); // general case for non array, object or string
}
}
// Shallow copy of array, we skip any sub-array or sub-object. It may be added in the future
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
for (auto v : arr) {
if (v.is<char*>()) {
String sval = v.as<String>(); // force a copy of the String value
to.add(sval);
} else if (v.is<JsonArray>()) {
} else if (v.is<JsonObject>()) {
} else {
to.add(v);
}
}
}
// Deep copy of object
void CopyJsonObject(JsonObject &to, const JsonObject &from) {
for (auto kv : from) {
String key_string = kv.key;
JsonVariant &val = kv.value;
CopyJsonVariant(to, key_string, val);
}
}
// does the new payload conflicts with the existing payload, i.e. values would be overwritten
// true - one attribute (except LinkQuality) woudl be lost, there is conflict
// false - new attributes can be safely added
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return false; }
bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const Z_attribute_list &attr_list) const {
const Z_Device & device = findShortAddr(shortaddr);
if (nullptr == device.json) {
if (!foundDevice(device)) { return false; }
if (attr_list.isEmpty()) {
return false; // if no previous value, no conflict
}
// compare groups
// Special case for group addresses. Group attribute is only present if the target
// address is a group address, so just comparing attributes will not work.
// Eg: if the first packet has no group attribute, and the second does, conflict would not be detected
// Here we explicitly compute the group address of both messages, and compare them. No group means group=0x0000
// (we use the property of an missing attribute returning 0)
// (note: we use .get() here which is case-sensitive. We know however that the attribute was set with the exact syntax D_CMND_ZIGBEE_GROUP, so we don't need a case-insensitive get())
uint16_t group1 = device.json->get<unsigned int>(D_CMND_ZIGBEE_GROUP);
uint16_t group2 = values.get<unsigned int>(D_CMND_ZIGBEE_GROUP);
if (group1 != group2) {
return true; // if group addresses differ, then conflict
if (device.attr_list.isValidGroupId() && attr_list.isValidGroupId()) {
if (device.attr_list.group_id != attr_list.group_id) { return true; } // groups are in conflict
}
// parse all other parameters
for (auto kv : values) {
String key_string = kv.key;
// 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
if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_GROUP))) {
// ignore group, it was handled already
} else if (0 == strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_ENDPOINT))) {
// attribute "Endpoint" or "Group"
if (device.json->containsKey(kv.key)) {
if (kv.value.as<unsigned int>() != device.json->get<unsigned int>(kv.key)) {
return true;
}
}
} else if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
if (device.json->containsKey(kv.key)) {
return true; // conflict!
// parse all other parameters
for (const auto & attr : attr_list) {
const Z_attribute * curr_attr = device.attr_list.findAttribute(attr);
if (nullptr != curr_attr) {
if (!curr_attr->equalsVal(attr)) {
return true; // the value already exists and is different - conflict!
}
}
}
return false;
}
void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) {
void Z_Devices::jsonAppend(uint16_t shortaddr, const Z_attribute_list &attr_list) {
Z_Device & device = getShortAddr(shortaddr);
if (&values == nullptr) { return; }
if (nullptr == device.json) {
device.json = &(device.json_buffer->createObject());
}
// Prepend Device, will be removed later if redundant
char sa[8];
snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr);
device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa);
// Prepend Friendly Name if it has one
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
if (fname) {
device.json->set(F(D_JSON_ZIGBEE_NAME), (char*) fname); // (char*) forces ArduinoJson to make a copy of the cstring
}
// copy all values from 'values' to 'json'
CopyJsonObject(*device.json, values);
}
const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
return getShortAddr(shortaddr).json;
device.attr_list.mergeList(attr_list);
}
void Z_Devices::jsonPublishFlush(uint16_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
if (!device.valid()) { return; } // safeguard
JsonObject & json = *device.json;
if (&json == nullptr) { return; } // abort if nothing in buffer
Z_attribute_list &attr_list = device.attr_list;
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
if (!attr_list.isEmpty()) {
const char * fname = zigbee_devices.getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
// save parameters is global variables to be used by Rules
gZbLastMessage.device = shortaddr; // %zbdevice%
gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup%
gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster%
gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint%
// save parameters is global variables to be used by Rules
gZbLastMessage.device = shortaddr; // %zbdevice%
gZbLastMessage.groupaddr = attr_list.group_id; // %zbgroup%
gZbLastMessage.endpoint = attr_list.src_ep; // %zbendpoint%
// dump json in string
String msg = "";
json.printTo(msg);
zigbee_devices.jsonClear(shortaddr);
if (use_fname) {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"%s\":%s}"), fname, msg.c_str());
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname, msg.c_str());
mqtt_data[0] = 0; // clear string
// Do we prefix with `ZbReceived`?
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":"));
}
} else {
if (Settings.flag4.remove_zbreceived) {
Response_P(PSTR("{\"0x%04X\":%s}"), shortaddr, msg.c_str());
// What key do we use, shortaddr or name?
if (use_fname) {
Response_P(PSTR("%s{\"%s\":{"), mqtt_data, fname);
} else {
Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
Response_P(PSTR("%s{\"0x%04X\":{"), mqtt_data, shortaddr);
}
// Add "Device":"0x...."
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\","), mqtt_data, shortaddr);
// Add "Name":"xxx" if name is present
if (fname) {
Response_P(PSTR("%s\"" D_JSON_ZIGBEE_NAME "\":\"%s\","), mqtt_data, EscapeJSONString(fname).c_str());
}
// Add all other attributes
Response_P(PSTR("%s%s}}"), mqtt_data, attr_list.toString().c_str());
if (!Settings.flag4.remove_zbreceived) {
Response_P(PSTR("%s}"), mqtt_data);
}
// AddLog_P2(LOG_LEVEL_INFO, PSTR(">>> %s"), mqtt_data); // TODO
attr_list.reset(); // clear the attributes
if (Settings.flag4.zigbee_distinct_topics) {
char subtopic[16];
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
} else {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
XdrvRulesProcess(); // apply rules
}
if (Settings.flag4.zigbee_distinct_topics) {
char subtopic[16];
snprintf_P(subtopic, sizeof(subtopic), PSTR("%04X/" D_RSLT_SENSOR), shortaddr);
MqttPublishPrefixTopic_P(TELE, subtopic, Settings.flag.mqtt_sensor_retain);
} else {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
}
XdrvRulesProcess(); // apply rules
}
void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) {
void Z_Devices::jsonPublishNow(uint16_t shortaddr, Z_attribute_list &attr_list) {
jsonPublishFlush(shortaddr); // flush any previous buffer
jsonAppend(shortaddr, values);
jsonAppend(shortaddr, attr_list);
jsonPublishFlush(shortaddr); // publish now
}
@ -1044,9 +922,8 @@ String Z_Devices::dumpLightState(uint16_t shortaddr) const {
JsonObject& json = jsonBuffer.createObject();
char hex[8];
int32_t found = findShortAddrIdx(shortaddr);
if (found >= 0) {
const Z_Device & device = devicesAt(found);
const Z_Device & device = findShortAddr(shortaddr);
if (foundDevice(device)) {
const char * fname = getFriendlyName(shortaddr);
bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname?
@ -1090,8 +967,7 @@ String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const {
JsonArray& json = jsonBuffer.createArray();
JsonArray& devices = json;
for (std::vector<Z_Device*>::const_iterator it = _devices.begin(); it != _devices.end(); ++it) {
const Z_Device &device = **it;
for (const auto & device : _devices) {
uint16_t shortaddr = device.shortaddr;
char hex[22];

File diff suppressed because it is too large Load Diff

View File

@ -174,7 +174,19 @@ int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clus
if (groupaddr) {
shortaddr = BAD_SHORTADDR; // if group address, don't send to device
}
ZigbeeZCLSend_Raw(shortaddr, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, 0, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(shortaddr));
uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
groupaddr,
cluster /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0, /* manuf */
false /* not cluster specific */,
true /* response */,
seq, /* zcl transaction id */
attrs, attrs_len
}));
}
return 0; // Fix GCC 10.1 warning
}
@ -188,31 +200,6 @@ int32_t Z_Unreachable(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster,
return 0; // Fix GCC 10.1 warning
}
// set a timer to read back the value in the future
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint) {
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006: // for On/Off
case 0x0009: // for Alamrs
wait_ms = 200; // wait 0.2 s
break;
case 0x0008: // for Dimmer
case 0x0300: // for Color
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102: // for Shutters
wait_ms = 10000; // wait 10.0 s
break;
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, Z_CAT_NONE, 0 /* value */, &Z_ReadAttrCallback);
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
// returns true if char is 'x', 'y' or 'z'
inline bool isXYZ(char c) {
return (c >= 'x') && (c <= 'z');
@ -280,61 +267,10 @@ void parseXYZ(const char *model, const SBuffer &payload, struct Z_XYZ_Var *xyz)
}
}
// works on big endiand hex only
// Returns if found:
// - cluster number
// - command number or 0xFF if command is part of the variable part
// - the payload in the form of a HEX string with x/y/z variables
void sendHueUpdate(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t cmd, bool direction) {
if (direction) { return; } // no need to update if server->client
int32_t z_cat = -1;
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006:
z_cat = Z_CAT_READ_0006;
wait_ms = 200; // wait 0.2 s
break;
case 0x0008:
z_cat = Z_CAT_READ_0008;
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102:
z_cat = Z_CAT_READ_0102;
wait_ms = 10000; // wait 10.0 s
break;
case 0x0300:
z_cat = Z_CAT_READ_0300;
wait_ms = 1050; // wait 1.0 s
break;
default:
break;
}
if (z_cat >= 0) {
uint8_t endpoint = 0;
if (BAD_SHORTADDR != shortaddr) {
endpoint = zigbee_devices.findFirstEndpoint(shortaddr);
}
if ((BAD_SHORTADDR == shortaddr) || (endpoint)) { // send if group address or endpoint is known
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms, cluster, endpoint, z_cat, 0 /* value */, &Z_ReadAttrCallback);
if (BAD_SHORTADDR != shortaddr) { // reachability test is not possible for group addresses, since we don't know the list of devices in the group
zigbee_devices.setTimer(shortaddr, groupaddr, wait_ms + Z_CAT_REACHABILITY_TIMEOUT, cluster, endpoint, Z_CAT_REACHABILITY, 0 /* value */, &Z_Unreachable);
}
}
}
}
// Parse a cluster specific command, and try to convert into human readable
void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
size_t hex_char_len = payload.len()*2+2;
char *hex_char = (char*) malloc(hex_char_len);
if (!hex_char) { return; }
ToHex_P((unsigned char*)payload.getBuffer(), payload.len(), hex_char, hex_char_len);
const __FlashStringHelper* command_name = nullptr;
void convertClusterSpecific(class Z_attribute_list &attr_list, uint16_t cluster, uint8_t cmd, bool direction, uint16_t shortaddr, uint8_t srcendpoint, const SBuffer &payload) {
const char * command_name = nullptr;
uint8_t conv_direction;
Z_XYZ_Var xyz;
@ -373,7 +309,7 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
p += 2;
}
if (match) {
command_name = (const __FlashStringHelper*) (Z_strings + pgm_read_word(&conv->tasmota_cmd_offset));
command_name = Z_strings + pgm_read_word(&conv->tasmota_cmd_offset);
parseXYZ(Z_strings + pgm_read_word(&conv->param_offset), payload, &xyz);
if (0xFF == conv_cmd) {
// shift all values
@ -396,112 +332,105 @@ void convertClusterSpecific(JsonObject& json, uint16_t cluster, uint8_t cmd, boo
// Format: "0004<00": "00" = "<cluster><<cmd>": "<payload>" for commands to devices
char attrid_str[12];
snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X%c%02X"), cluster, direction ? '<' : '!', cmd);
json[attrid_str] = hex_char;
free(hex_char);
attr_list.addAttribute(attrid_str).setBuf(payload, 0, payload.len());
if (command_name) {
// Now try to transform into a human readable format
String command_name2 = String(command_name);
// if (direction & 0x80) then specific transform
if (conv_direction & 0x80) {
// TODO need to create a specific command
uint32_t cccc00mm = (cluster << 16) | cmd; // format = cccc00mm, cccc = cluster, mm = command
// IAS
if ((cluster == 0x0500) && (cmd == 0x00)) {
// "ZoneStatusChange"
json[command_name] = xyz.x;
switch (cccc00mm) {
case 0x05000000: // "ZoneStatusChange"
attr_list.addAttribute(command_name, true).setUInt(xyz.x);
if (0 != xyz.y) {
json[command_name2 + F("Ext")] = xyz.y;
attr_list.addAttribute(command_name, PSTR("Ext")).setUInt(xyz.y);
}
if ((0 != xyz.z) && (0xFF != xyz.z)) {
json[command_name2 + F("Zone")] = xyz.z;
attr_list.addAttribute(command_name, PSTR("Zone")).setUInt(xyz.z);
}
} else if ((cluster == 0x0004) && ((cmd == 0x00) || (cmd == 0x01) || (cmd == 0x03))) {
// AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
json[command_name] = xyz.y;
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
} else if ((cluster == 0x0004) && (cmd == 0x02)) {
// GetGroupResp
json[command_name2 + F("Capacity")] = xyz.x;
json[command_name2 + F("Count")] = xyz.y;
JsonArray &arr = json.createNestedArray(command_name);
for (uint32_t i = 0; i < xyz.y; i++) {
arr.add(payload.get16(2 + 2*i));
break;
case 0x00040000:
case 0x00040001:
case 0x00040003: // AddGroupResp or ViewGroupResp (group name ignored) or RemoveGroup
attr_list.addAttribute(command_name, true).setUInt(xyz.y);
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
break;
case 0x00040002: // GetGroupResp
attr_list.addAttribute(command_name, PSTR("Capacity")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("Count")).setUInt(xyz.y);
{
Z_json_array group_list;
for (uint32_t i = 0; i < xyz.y; i++) {
group_list.add(payload.get16(2 + 2*i));
}
attr_list.addAttribute(command_name, true).setStrRaw(group_list.toString().c_str());
}
} else if ((cluster == 0x0005) && ((cmd == 0x00) || (cmd == 0x02) || (cmd == 0x03))) {
// AddScene or RemoveScene or StoreScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
} else if ((cluster == 0x0005) && (cmd == 0x01)) {
// ViewScene
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
json[F("SceneId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0005) && (cmd == 0x03)) {
// RemoveAllScenes
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("GroupId")] = xyz.y;
} else if ((cluster == 0x0005) && (cmd == 0x06)) {
// GetSceneMembership
json[command_name2 + F("Status")] = xyz.x;
json[command_name2 + F("StatusMsg")] = getZigbeeStatusMessage(xyz.x);
json[F("Capacity")] = xyz.y;
json[F("GroupId")] = xyz.z;
String scene_payload = json[attrid_str];
json[F("ScenePayload")] = scene_payload.substring(8); // remove first 8 characters
} else if ((cluster == 0x0006) && (cmd == 0x40)) {
// Power Off With Effect
json[F("Power")] = 0; // always "Power":0
json[F("PowerEffect")] = xyz.x;
json[F("PowerEffectVariant")] = xyz.y;
} else if ((cluster == 0x0006) && (cmd == 0x41)) {
// Power On With Recall Global Scene
json[F("Power")] = 1; // always "Power":1
json[F("PowerRecallGlobalScene")] = true;
} else if ((cluster == 0x0006) && (cmd == 0x42)) {
// Power On With Timed Off Command
json[F("Power")] = 1; // always "Power":1
json[F("PowerOnlyWhenOn")] = xyz.x;
json[F("PowerOnTime")] = xyz.y / 10.0f;
json[F("PowerOffWait")] = xyz.z / 10.0f;
break;
case 0x00050000:
case 0x00050001: // ViewScene
case 0x00050002:
case 0x00050004: // AddScene or RemoveScene or StoreScene
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("SceneId"), true).setUInt(xyz.z);
if (0x00050001 == cccc00mm) { // ViewScene specific
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
}
break;
case 0x00050003: // RemoveAllScenes
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.y);
break;
case 0x00050006: // GetSceneMembership
attr_list.addAttribute(command_name, PSTR("Status")).setUInt(xyz.x);
attr_list.addAttribute(command_name, PSTR("StatusMsg")).setStr(getZigbeeStatusMessage(xyz.x).c_str());
attr_list.addAttribute(PSTR("Capacity"), true).setUInt(xyz.y);
attr_list.addAttribute(PSTR("GroupId"), true).setUInt(xyz.z);
attr_list.addAttribute(PSTR("ScenePayload"), true).setBuf(payload, 4, payload.len()-4); // remove first 4 bytes
break;
case 0x00060040: // Power Off With Effect
attr_list.addAttribute(PSTR("Power"), true).setUInt(0);
attr_list.addAttribute(PSTR("PowerEffect"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerEffectVariant"), true).setUInt(xyz.y);
break;
case 0x00060041: // Power On With Recall Global Scene
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerRecallGlobalScene"), true).setBool(true);
break;
case 0x00060042: // Power On With Timed Off Command
attr_list.addAttribute(PSTR("Power"), true).setUInt(1);
attr_list.addAttribute(PSTR("PowerOnlyWhenOn"), true).setUInt(xyz.x);
attr_list.addAttribute(PSTR("PowerOnTime"), true).setFloat(xyz.y / 10.0f);
attr_list.addAttribute(PSTR("PowerOffWait"), true).setFloat(xyz.z / 10.0f);
break;
}
} else { // general case
bool extended_command = false; // do we send command with endpoint suffix
// do we send command with endpoint suffix
char command_suffix[4] = { 0x00 }; // empty string by default
// if SO101 and multiple endpoints, append endpoint number
if (Settings.flag4.zb_index_ep) {
if (zigbee_devices.countEndpoints(shortaddr) > 0) {
command_name2 += srcendpoint;
extended_command = true;
snprintf_P(command_suffix, sizeof(command_suffix), PSTR("%d"), srcendpoint);
}
}
if (0 == xyz.x_type) {
json[command_name] = true; // no parameter
if (extended_command) { json[command_name2] = true; }
attr_list.addAttribute(command_name, command_suffix).setBool(true);
} else if (0 == xyz.y_type) {
json[command_name] = xyz.x; // 1 parameter
if (extended_command) { json[command_name2] = xyz.x; }
attr_list.addAttribute(command_name, command_suffix).setUInt(xyz.x);
} else {
// multiple answers, create an array
JsonArray &arr = json.createNestedArray(command_name);
Z_json_array arr;
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
if (extended_command) {
JsonArray &arr = json.createNestedArray(command_name2);
arr.add(xyz.x);
arr.add(xyz.y);
if (xyz.z_type) {
arr.add(xyz.z);
}
}
attr_list.addAttribute(command_name, command_suffix).setStrRaw(arr.toString().c_str());
}
}
}

View File

@ -969,9 +969,18 @@ void Z_SendAFInfoRequest(uint16_t shortaddr) {
uint8_t InfoReq[] = { 0x04, 0x00, 0x05, 0x00 };
ZigbeeZCLSend_Raw(shortaddr, 0x0000 /*group*/, 0x0000 /*cluster*/, endpoint, ZCL_READ_ATTRIBUTES,
false /*clusterSpecific*/, 0x0000 /*manuf*/,
InfoReq, sizeof(InfoReq), true /*needResponse*/, transacid);
ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({
shortaddr,
0x0000, /* group */
0x0000 /*cluster*/,
endpoint,
ZCL_READ_ATTRIBUTES,
0x0000, /* manuf */
false /* not cluster specific */,
true /* response */,
transacid, /* zcl transaction id */
InfoReq, sizeof(InfoReq)
}));
}
@ -1011,53 +1020,53 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) {
// Parse incoming ZCL message.
//
// This code is common to ZNP and EZSP
void Z_IncomingMessage(ZCLFrame &zcl_received) {
void Z_IncomingMessage(class ZCLFrame &zcl_received) {
uint16_t srcaddr = zcl_received.getSrcAddr();
uint16_t groupid = zcl_received.getGroupAddr();
uint16_t clusterid = zcl_received.getClusterId();
uint8_t linkquality = zcl_received.getLinkQuality();
uint8_t srcendpoint = zcl_received.getSrcEndpoint();
linkquality = linkquality != 0xFF ? linkquality : 0xFE; // avoid 0xFF (reserved for unknown)
bool defer_attributes = false; // do we defer attributes reporting to coalesce
// log the packet details
zcl_received.log();
zigbee_devices.setLQI(srcaddr, linkquality != 0xFF ? linkquality : 0xFE); // EFR32 has a different scale for LQI
zigbee_devices.setLQI(srcaddr, linkquality); // EFR32 has a different scale for LQI
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
DynamicJsonBuffer jsonBuffer;
JsonObject& json = jsonBuffer.createObject();
Z_attribute_list attr_list;
attr_list.lqi = linkquality;
attr_list.src_ep = srcendpoint;
if (groupid) { // TODO we miss the group_id == 0 here
attr_list.group_id = groupid;
}
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message
zcl_received.parseResponse(); // Zigbee general "Default Response", publish ZbResponse message
} else {
// Build the ZbReceive json
// Build the ZbReceive list
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors
zcl_received.parseReportAttributes(attr_list); // Zigbee report attributes from sensors
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributesResponse(json);
zcl_received.parseReadAttributesResponse(attr_list);
if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json);
zcl_received.parseReadAttributes(attr_list);
// never defer read_attributes, so the auto-responder can send response back on a per cluster basis
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_REPORTING_CONFIGURATION_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadConfigAttributes(json);
zcl_received.parseReadConfigAttributes(attr_list);
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_CONFIGURE_REPORTING_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseConfigAttributes(json);
zcl_received.parseConfigAttributes(attr_list);
} else if (zcl_received.isClusterSpecificCommand()) {
zcl_received.parseClusterSpecificCommand(json);
zcl_received.parseClusterSpecificCommand(attr_list);
}
{ // fence to force early de-allocation of msg
String msg("");
msg.reserve(100);
json.printTo(msg);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str());
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":{%s}}"), srcaddr, attr_list.toString().c_str());
// discard the message if it was sent by us (broadcast or group loopback)
if (srcaddr == localShortAddr) {
@ -1065,37 +1074,25 @@ void Z_IncomingMessage(ZCLFrame &zcl_received) {
return; // abort the rest of message management
}
zcl_received.postProcessAttributes(srcaddr, json);
// Add Endpoint
json[F(D_CMND_ZIGBEE_ENDPOINT)] = srcendpoint;
// Add Group if non-zero
if (groupid) {
json[F(D_CMND_ZIGBEE_GROUP)] = groupid;
}
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
zcl_received.generateSyntheticAttributes(attr_list);
zcl_received.generateCallBacks(attr_list); // set deferred callbacks, ex: Occupancy
zcl_received.postProcessAttributes(srcaddr, attr_list);
// since we just receveived data from the device, it is reachable
zigbee_devices.resetTimersForDevice(srcaddr, 0 /* groupaddr */, Z_CAT_REACHABILITY); // remove any reachability timer already there
zigbee_devices.setReachable(srcaddr, true); // mark device as reachable
// Post-provess for Aqara Presence Senson
Z_AqaraOccupancy(srcaddr, clusterid, srcendpoint, json);
if (defer_attributes) {
// Prepare for publish
if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
if (zigbee_devices.jsonIsConflict(srcaddr, attr_list)) {
// there is conflicting values, force a publish of the previous message now and don't coalesce
zigbee_devices.jsonPublishFlush(srcaddr);
}
zigbee_devices.jsonAppend(srcaddr, json);
zigbee_devices.jsonAppend(srcaddr, attr_list);
zigbee_devices.setTimer(srcaddr, 0 /* groupaddr */, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, Z_CAT_READ_ATTR, 0, &Z_PublishAttributes);
} else {
// Publish immediately
zigbee_devices.jsonPublishNow(srcaddr, json);
// Add auto-responder here
Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]);
zigbee_devices.jsonPublishNow(srcaddr, attr_list);
}
}
}
@ -1281,30 +1278,8 @@ int32_t EZ_Recv_Default(int32_t res, const class SBuffer &buf) {
* Callbacks
\*********************************************************************************************/
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
static const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
// Read OCCUPANCY value if any
const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(OCCUPANCY));
if (nullptr != &val_endpoint) {
uint32_t occupancy = strToUInt(val_endpoint);
if (occupancy) {
zigbee_devices.setTimer(shortaddr, 0 /* groupaddr */, OCCUPANCY_TIMEOUT, cluster, endpoint, Z_CAT_VIRTUAL_OCCUPANCY, 0, &Z_OccupancyCallback);
} else {
zigbee_devices.resetTimersForDevice(shortaddr, 0 /* groupaddr */, Z_CAT_VIRTUAL_OCCUPANCY);
}
}
}
// Publish the received values once they have been coalesced
int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint32_t value) {
const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
if (json == nullptr) { return 0; } // don't crash if not found
zigbee_devices.jsonPublishFlush(shortaddr);
return 1;
}
@ -1476,49 +1451,61 @@ int32_t Z_State_Ready(uint8_t value) {
//
// Mostly used for routers/end-devices
// json: holds the attributes in JSON format
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) {
void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const uint16_t *attr_list, size_t attr_len) {
DynamicJsonBuffer jsonBuffer;
JsonObject& json_out = jsonBuffer.createObject();
// responder
switch (cluster) {
case 0x0000:
if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); }
if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); }
break;
for (uint32_t i=0; i<attr_len; i++) {
uint16_t attr = attr_list[i];
uint32_t ccccaaaa = (cluster << 16) || attr;
switch (ccccaaaa) {
case 0x00000004: json_out[F("Manufacturer")] = F(USE_ZIGBEE_MANUFACTURER); break; // Manufacturer
case 0x00000005: json_out[F("ModelId")] = F(USE_ZIGBEE_MODELID); break; // ModelId
#ifdef USE_LIGHT
case 0x0006:
if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; }
break;
case 0x0008:
if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); }
break;
case 0x0300:
{
uint16_t hue;
uint8_t sat;
float XY[2];
LightGetHSB(&hue, &sat, nullptr);
LightGetXY(&XY[0], &XY[1]);
uint16_t uxy[2];
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
uxy[i] = XY[i] * 65536.0f;
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
}
if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); }
if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; }
if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; }
}
break;
case 0x00060000: json_out[F("Power")] = Light.power ? 1 : 0; break; // Power
case 0x00080000: json_out[F("Dimmer")] = LightGetDimmer(0); break; // Dimmer
case 0x03000000: // Hue
case 0x03000001: // Sat
case 0x03000003: // X
case 0x03000004: // Y
case 0x03000007: // CT
{
uint16_t hue;
uint8_t sat;
float XY[2];
LightGetHSB(&hue, &sat, nullptr);
LightGetXY(&XY[0], &XY[1]);
uint16_t uxy[2];
for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) {
uxy[i] = XY[i] * 65536.0f;
uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF;
}
if (0x0000 == attr) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); }
if (0x0001 == attr) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); }
if (0x0003 == attr) { json_out[F("X")] = uxy[0]; }
if (0x0004 == attr) { json_out[F("Y")] = uxy[1]; }
if (0x0007 == attr) { json_out[F("CT")] = LightGetColorTemp(); }
}
break;
#endif
case 0x000A: // Time
if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeEpoch"))) { json_out[F("TimeEpoch")] = Rtc.utc_time; }
if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized
if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds
break;
case 0x000A0000: // Time
json_out[F("Time")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time;
break;
case 0x000AFF00: // TimeEpoch - Tasmota specific
json_out[F("TimeEpoch")] = Rtc.utc_time;
break;
case 0x000A0001: // TimeStatus
json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; // if time is beyond 2010 then we are synchronized
break;
case 0x000A0002: // TimeZone
json_out[F("TimeZone")] = Settings.toffset[0] * 60;
break;
case 0x000A0007: // LocalTime // TODO take DST
json_out[F("LocalTime")] = Settings.toffset[0] * 60 + ((Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? Rtc.utc_time - 946684800 : Rtc.utc_time);
break;
}
}
if (json_out.size() > 0) {

View File

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

View File

@ -491,28 +491,25 @@ const char HTTP_SCRIPT_XFER_STATE[] PROGMEM =
"if(x.readyState==4&&x.status==200){"
"var s=x.responseText;"
"if(s!=7){" // ZBU_UPLOAD
"location.href='/u3';"
"location.href='/u3';" // Load page HandleUploadDone()
"}"
"}"
"};"
"x.open('GET','" WEB_HANDLE_ZIGBEE_XFER "?m=1',true);" // ?m related to Webserver->hasArg("m")
"x.open('GET','" WEB_HANDLE_ZIGBEE_XFER "?z=1',true);" // ?z related to Webserver->hasArg("z")
"x.send();"
"if(pc==1){"
"lt=setTimeout(z9,950);" // Poll every 0.95 second
"}"
"lt=setTimeout(z9,950);" // Poll every 0.95 second
"}"
"pc=1;"
"wl(z9);";
"wl(z9);"; // Execute z9() on page load
void HandleZigbeeXfer(void) {
if (!HttpCheckPriviledgedAccess()) { return; }
if (Webserver->hasArg("m")) { // Status refresh requested
if (Webserver->hasArg("z")) { // Status refresh requested
WSContentBegin(200, CT_PLAIN);
WSContentSend_P(PSTR("%d"), ZbUpload.state);
WSContentEnd();
if (ZBU_ERROR == ZbUpload.state) {
Web.upload_error = 7; // Upload aborted (failed)
Web.upload_error = 7; // Upload aborted (xmodem transfer failed)
}
return;
}
@ -522,7 +519,7 @@ void HandleZigbeeXfer(void) {
WSContentStart_P(S_INFORMATION);
WSContentSend_P(HTTP_SCRIPT_XFER_STATE);
WSContentSendStyle();
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_UPLOAD_TRANSFER "...</b></div>"));
WSContentSend_P(PSTR("<div style='text-align:center;'><b>" D_UPLOAD_TRANSFER " ...</b></div>"));
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}

View File

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

View File

@ -458,6 +458,10 @@ void TasmotaClient_Init(void) {
}
}
bool TasmotaClient_Available(void) {
return TClient.SerialEnabled;
}
void TasmotaClient_Show(void) {
if ((TClient.type) && (TClientSettings.features.func_json_append)) {
char buffer[100];

View File

@ -27,18 +27,12 @@
#define COLORED 1
#define UNCOLORED 0
// touch panel controller
#define FT6236_address 0x38
// using font 8 is opional (num=3)
// very badly readable, but may be useful for graphs
#define USE_TINY_FONT
#include <ILI9488.h>
#include <FT6236.h>
TouchLocation ili9488_pLoc;
uint8_t ili9488_ctouch_counter = 0;
// currently fixed
@ -47,13 +41,7 @@ uint8_t ili9488_ctouch_counter = 0;
extern uint8_t *buffer;
extern uint8_t color_type;
ILI9488 *ili9488;
#ifdef USE_TOUCH_BUTTONS
extern VButton *buttons[];
#endif
extern const uint16_t picture[];
uint8_t FT6236_found;
/*********************************************************************************************/
@ -126,131 +114,52 @@ void ILI9488_InitDriver()
#endif
color_type = COLOR_COLOR;
// start digitizer with fixed adress
if (I2cEnabled(XI2C_38) && I2cSetDevice(FT6236_address)) {
FT6236begin(FT6236_address);
FT6236_found=1;
I2cSetActiveFound(FT6236_address, "FT6236");
} else {
FT6236_found=0;
}
// start digitizer
#ifdef USE_FT5206
Touch_Init(Wire);
#endif
}
}
#ifdef USE_FT5206
#ifdef USE_TOUCH_BUTTONS
void ILI9488_MQTT(uint8_t count,const char *cp) {
ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7);
MqttPublishTeleSensor();
}
void ILI9488_RDW_BUTT(uint32_t count,uint32_t pwr) {
buttons[count]->xdrawButton(pwr);
if (pwr) buttons[count]->vpower|=0x80;
else buttons[count]->vpower&=0x7f;
}
// check digitizer hit
void FT6236Check() {
uint16_t temp;
uint8_t rbutt=0,vbutt=0;
ili9488_ctouch_counter++;
if (2 == ili9488_ctouch_counter) {
// every 100 ms should be enough
ili9488_ctouch_counter=0;
if (FT6236readTouchLocation(&ili9488_pLoc,1)) {
// did find a hit
if (renderer) {
uint8_t rot=renderer->getRotation();
switch (rot) {
case 0:
temp=ili9488_pLoc.y;
ili9488_pLoc.y=renderer->height()-ili9488_pLoc.x;
ili9488_pLoc.x=temp;
break;
case 1:
break;
case 2:
break;
case 3:
temp=ili9488_pLoc.y;
ili9488_pLoc.y=ili9488_pLoc.x;
ili9488_pLoc.x=renderer->width()-temp;
break;
}
// now must compare with defined buttons
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
if (buttons[count]->contains(ili9488_pLoc.x,ili9488_pLoc.y)) {
// did hit
buttons[count]->press(true);
if (buttons[count]->justPressed()) {
if (!bflags) {
uint8_t pwr=bitRead(power,rbutt);
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
ILI9488_RDW_BUTT(count,!pwr);
}
} else {
// virtual button
const char *cp;
if (bflags==1) {
// toggle button
buttons[count]->vpower^=0x80;
cp="TBT";
} else {
// push button
buttons[count]->vpower|=0x80;
cp="PBT";
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
ILI9488_MQTT(count,cp);
}
}
}
if (!bflags) {
rbutt++;
} else {
vbutt++;
}
}
}
void ILI9488_RotConvert(int16_t *x, int16_t *y) {
int16_t temp;
if (renderer) {
uint8_t rot=renderer->getRotation();
switch (rot) {
case 0:
temp=*y;
*y=renderer->height()-*x;
*x=temp;
break;
case 1:
break;
case 2:
break;
case 3:
temp=*y;
*y=*x;
*x=renderer->width()-temp;
break;
}
} else {
// no hit
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
buttons[count]->press(false);
if (buttons[count]->justReleased()) {
uint8_t bflags=buttons[count]->vpower&0x7f;
if (bflags>0) {
if (bflags>1) {
// push button
buttons[count]->vpower&=0x7f;
ILI9488_MQTT(count,"PBT");
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
}
}
if (!bflags) {
// check if power button stage changed
uint8_t pwr=bitRead(power,rbutt);
uint8_t vpwr=(buttons[count]->vpower&0x80)>>7;
if (pwr!=vpwr) {
ILI9488_RDW_BUTT(count,pwr);
}
rbutt++;
}
}
}
ili9488_pLoc.x=0;
ili9488_pLoc.y=0;
}
}
// check digitizer hit
void ILI9488_CheckTouch(void) {
ili9488_ctouch_counter++;
if (2 == ili9488_ctouch_counter) {
// every 100 ms should be enough
ili9488_ctouch_counter = 0;
Touch_Check(ILI9488_RotConvert);
}
}
#endif // USE_TOUCH_BUTTONS
#endif // USE_FT5206
/*********************************************************************************************/
/*********************************************************************************************\
* Interface
@ -270,7 +179,9 @@ bool Xdsp08(uint8_t function)
break;
case FUNC_DISPLAY_EVERY_50_MSECOND:
#ifdef USE_TOUCH_BUTTONS
if (FT6236_found) FT6236Check();
if (FT5206_found) {
ILI9488_CheckTouch();
}
#endif
break;
}

View File

@ -27,29 +27,17 @@
#define COLORED 1
#define UNCOLORED 0
// touch panel controller
#define FT5316_address 0x38
// using font 8 is opional (num=3)
// very badly readable, but may be useful for graphs
#define USE_TINY_FONT
#include <RA8876.h>
#include <FT6236.h>
TouchLocation ra8876_pLoc;
uint8_t ra8876_ctouch_counter = 0;
#ifdef USE_TOUCH_BUTTONS
extern VButton *buttons[];
#endif
extern uint8_t *buffer;
extern uint8_t color_type;
RA8876 *ra8876;
uint8_t FT5316_found;
/*********************************************************************************************/
void RA8876_InitDriver()
{
@ -114,147 +102,41 @@ void RA8876_InitDriver()
#endif
color_type = COLOR_COLOR;
if (I2cEnabled(XI2C_39) && I2cSetDevice(FT5316_address)) {
FT6236begin(FT5316_address);
FT5316_found=1;
I2cSetActiveFound(FT5316_address, "FT5316");
} else {
FT5316_found=0;
}
#ifdef USE_FT5206
Touch_Init(Wire);
#endif
}
}
#ifdef USE_TOUCH_BUTTONS
void RA8876_MQTT(uint8_t count,const char *cp) {
ResponseTime_P(PSTR(",\"RA8876\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7);
MqttPublishTeleSensor();
}
void RA8876_RDW_BUTT(uint32_t count,uint32_t pwr) {
buttons[count]->xdrawButton(pwr);
if (pwr) buttons[count]->vpower|=0x80;
else buttons[count]->vpower&=0x7f;
#ifdef USE_FT5206
#ifdef USE_TOUCH_BUTTONS
// no rotation support
void RA8876_RotConvert(int16_t *x, int16_t *y) {
int16_t temp;
if (renderer) {
*x=*x*renderer->width()/800;
*y=*y*renderer->height()/480;
*x = renderer->width() - *x;
*y = renderer->height() - *y;
}
}
// check digitizer hit
void FT5316Check() {
uint16_t temp;
uint8_t rbutt=0,vbutt=0;
ra8876_ctouch_counter++;
if (2 == ra8876_ctouch_counter) {
// every 100 ms should be enough
ra8876_ctouch_counter=0;
// panel has 800x480
if (FT6236readTouchLocation(&ra8876_pLoc,1)) {
ra8876_pLoc.x=ra8876_pLoc.x*RA8876_TFTWIDTH/800;
ra8876_pLoc.y=ra8876_pLoc.y*RA8876_TFTHEIGHT/480;
// did find a hit
if (renderer) {
// rotation not supported
ra8876_pLoc.x=RA8876_TFTWIDTH-ra8876_pLoc.x;
ra8876_pLoc.y=RA8876_TFTHEIGHT-ra8876_pLoc.y;
/*
uint8_t rot=renderer->getRotation();
switch (rot) {
case 0:
//temp=pLoc.y;
pLoc.x=renderer->width()-pLoc.x;
pLoc.y=renderer->height()-pLoc.y;
//pLoc.x=temp;
break;
case 1:
break;
case 2:
break;
case 3:
temp=pLoc.y;
pLoc.y=pLoc.x;
pLoc.x=renderer->width()-temp;
break;
}
*/
//AddLog_P2(LOG_LEVEL_INFO, PSTR(">> %d,%d"),ra8876_pLoc.x,ra8876_pLoc.y);
//Serial.printf("loc x: %d , loc y: %d\n",pLoc.x,pLoc.y);
// now must compare with defined buttons
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
if (buttons[count]->contains(ra8876_pLoc.x,ra8876_pLoc.y)) {
// did hit
buttons[count]->press(true);
if (buttons[count]->justPressed()) {
if (!bflags) {
// real button
uint8_t pwr=bitRead(power,rbutt);
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
RA8876_RDW_BUTT(count,!pwr);
}
} else {
// virtual button
const char *cp;
if (bflags==1) {
// toggle button
buttons[count]->vpower^=0x80;
cp="TBT";
} else {
// push button
buttons[count]->vpower|=0x80;
cp="PBT";
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
RA8876_MQTT(count,cp);
}
}
}
if (!bflags) {
rbutt++;
} else {
vbutt++;
}
}
}
}
} else {
// no hit
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
buttons[count]->press(false);
if (buttons[count]->justReleased()) {
if (bflags>0) {
if (bflags>1) {
// push button
buttons[count]->vpower&=0x7f;
RA8876_MQTT(count,"PBT");
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
}
}
if (!bflags) {
// check if power button stage changed
uint8_t pwr=bitRead(power,rbutt);
uint8_t vpwr=(buttons[count]->vpower&0x80)>>7;
if (pwr!=vpwr) {
RA8876_RDW_BUTT(count,pwr);
}
rbutt++;
}
}
}
ra8876_pLoc.x=0;
ra8876_pLoc.y=0;
void RA8876_CheckTouch(void) {
ra8876_ctouch_counter++;
if (2 == ra8876_ctouch_counter) {
// every 100 ms should be enough
ra8876_ctouch_counter = 0;
Touch_Check(RA8876_RotConvert);
}
}
}
#endif // USE_TOUCH_BUTTONS
#endif // USE_TOUCH_BUTTONS
#endif // USE_FT5206
/*
void testall() {
ra8876->clearScreen(0);
@ -452,8 +334,8 @@ bool Xdsp10(uint8_t function)
result = true;
break;
case FUNC_DISPLAY_EVERY_50_MSECOND:
#ifdef USE_TOUCH_BUTTONS
if (FT5316_found) FT5316Check();
#ifdef USE_FT5206
if (FT5206_found) RA8876_CheckTouch();
#endif
break;
}

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//#ifdef USE_SPI
#ifdef USE_SPI
#ifdef USE_DISPLAY
#ifdef USE_DISPLAY_ST7789
@ -50,12 +51,6 @@ extern uint8_t color_type;
Arduino_ST7789 *st7789;
#ifdef USE_FT5206
#ifdef USE_TOUCH_BUTTONS
extern VButton *buttons[];
#endif
FT5206_Class *touchp;
uint8_t FT5206_found;
TP_Point st7789_pLoc;
uint8_t st7789_ctouch_counter = 0;
#endif // USE_FT5206
@ -142,155 +137,51 @@ void ST7789_InitDriver()
#define SDA_2 23
#define SCL_2 32
Wire1.begin(SDA_2, SCL_2, 400000);
touchp = new FT5206_Class();
if (touchp->begin(Wire1, FT5206_address)) {
FT5206_found=1;
//I2cSetDevice(FT5206_address);
I2cSetActiveFound(FT5206_address, "FT5206");
} else {
FT5206_found=0;
}
Touch_Init(Wire1);
#endif // USE_FT5206
#endif // ESP32
}
}
#ifdef ESP32
#ifdef USE_FT5206
#ifdef USE_TOUCH_BUTTONS
void ST7789_MQTT(uint8_t count,const char *cp) {
ResponseTime_P(PSTR(",\"ST7789\":{\"%s%d\":\"%d\"}}"), cp,count+1,(buttons[count]->vpower&0x80)>>7);
MqttPublishTeleSensor();
}
uint32_t FT5206_touched(uint32_t sel) {
if (touchp) {
switch (sel) {
void ST7789_RotConvert(int16_t *x, int16_t *y) {
int16_t temp;
if (renderer) {
uint8_t rot=renderer->getRotation();
switch (rot) {
case 0:
return touchp->touched();
break;
case 1:
return st7789_pLoc.x;
temp=*y;
*y=renderer->height()-*x;
*x=temp;
break;
case 2:
return st7789_pLoc.y;
*x=renderer->width()-*x;
*y=renderer->height()-*y;
break;
case 3:
temp=*y;
*y=*x;
*x=renderer->width()-temp;
break;
}
return 0;
} else {
return 0;
}
}
void ST7789_RDW_BUTT(uint32_t count,uint32_t pwr) {
buttons[count]->xdrawButton(pwr);
if (pwr) buttons[count]->vpower|=0x80;
else buttons[count]->vpower&=0x7f;
}
// check digitizer hit
void FT5206Check() {
uint16_t temp;
uint8_t rbutt=0,vbutt=0;
void ST7789_CheckTouch() {
st7789_ctouch_counter++;
if (2 == st7789_ctouch_counter) {
// every 100 ms should be enough
st7789_ctouch_counter=0;
if (touchp->touched()) {
// did find a hit
st7789_pLoc = touchp->getPoint(0);
if (renderer) {
uint8_t rot=renderer->getRotation();
switch (rot) {
case 0:
break;
case 1:
temp=st7789_pLoc.y;
st7789_pLoc.y=renderer->height()-st7789_pLoc.x;
st7789_pLoc.x=temp;
break;
case 2:
st7789_pLoc.x=renderer->width()-st7789_pLoc.x;
st7789_pLoc.y=renderer->height()-st7789_pLoc.y;
break;
case 3:
temp=st7789_pLoc.y;
st7789_pLoc.y=st7789_pLoc.x;
st7789_pLoc.x=renderer->width()-temp;
break;
}
//AddLog_P2(LOG_LEVEL_INFO, PSTR("touch %d - %d"), st7789_pLoc.x, st7789_pLoc.y);
// now must compare with defined buttons
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
if (buttons[count]->contains(st7789_pLoc.x,st7789_pLoc.y)) {
// did hit
buttons[count]->press(true);
if (buttons[count]->justPressed()) {
if (!bflags) {
uint8_t pwr=bitRead(power,rbutt);
if (!SendKey(KEY_BUTTON, rbutt+1, POWER_TOGGLE)) {
ExecuteCommandPower(rbutt+1, POWER_TOGGLE, SRC_BUTTON);
ST7789_RDW_BUTT(count,!pwr);
}
} else {
// virtual button
const char *cp;
if (bflags==1) {
// toggle button
buttons[count]->vpower^=0x80;
cp="TBT";
} else {
// push button
buttons[count]->vpower|=0x80;
cp="PBT";
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
ST7789_MQTT(count,cp);
}
}
}
if (!bflags) {
rbutt++;
} else {
vbutt++;
}
}
}
}
} else {
// no hit
for (uint8_t count=0; count<MAXBUTTONS; count++) {
if (buttons[count]) {
uint8_t bflags=buttons[count]->vpower&0x7f;
buttons[count]->press(false);
if (buttons[count]->justReleased()) {
uint8_t bflags=buttons[count]->vpower&0x7f;
if (bflags>0) {
if (bflags>1) {
// push button
buttons[count]->vpower&=0x7f;
ST7789_MQTT(count,"PBT");
}
buttons[count]->xdrawButton(buttons[count]->vpower&0x80);
}
}
if (!bflags) {
// check if power button stage changed
uint8_t pwr=bitRead(power,rbutt);
uint8_t vpwr=(buttons[count]->vpower&0x80)>>7;
if (pwr!=vpwr) {
ST7789_RDW_BUTT(count,pwr);
}
rbutt++;
}
}
}
st7789_pLoc.x=0;
st7789_pLoc.y=0;
if (2 == st7789_ctouch_counter) {
// every 100 ms should be enough
st7789_ctouch_counter = 0;
Touch_Check(ST7789_RotConvert);
}
}
}
#endif // USE_TOUCH_BUTTONS
#endif // USE_FT5206
#endif // ESP32
@ -317,7 +208,7 @@ bool Xdsp12(uint8_t function)
#ifdef USE_FT5206
#ifdef USE_TOUCH_BUTTONS
if (FT5206_found) {
FT5206Check();
ST7789_CheckTouch();
}
#endif
#endif // USE_FT5206

View File

@ -31,15 +31,8 @@
#define D_NAME_AS3935 "AS3935"
#define AS3935_ADDR 0x03
// Reg mask shift
#define IRQ_TBL 0x03, 0x0F, 0
#define ENERGY_RAW_1 0x04, 0xFF, 0
#define ENERGY_RAW_2 0x05, 0xFF, 0
#define ENERGY_RAW_3 0x06, 0x1F, 0
#define LGHT_DIST 0x07, 0x3F, 0
#define DISP_TRCO 0x08, 0x20, 5
#define DISP_LCO 0x08, 0x80, 7
#define TUNE_CAPS 0x08, 0x0F, 0
// I2C Registers Reg mask shift
#define PWR_REG 0x00, 0x01, 0
#define AFE_GB 0x00, 0x3E, 0
#define WDTH 0x01, 0x0F, 0
#define NF_LEVEL 0x01, 0x70, 4
@ -47,12 +40,32 @@
#define MIN_NUM_LIGH 0x02, 0x30, 4
#define DISTURBER 0x03, 0x20, 5
#define LCO_FDIV 0x03, 0xC0, 6
#define IRQ_TBL 0x03, 0x0F, 0
#define ENERGY_RAW_1 0x04, 0xFF, 0
#define ENERGY_RAW_2 0x05, 0xFF, 0
#define ENERGY_RAW_3 0x06, 0x1F, 0
#define LGHT_DIST 0x07, 0x3F, 0
#define DISP_TRCO 0x08, 0x20, 5 // should 31.250 kHz with devide 16
#define DISP_SRCO 0x08, 0x40, 6 // 1,1 MHz
#define DISP_LCO 0x08, 0x80, 7 // 32.768 kHz
#define TUNE_CAPS 0x08, 0x0F, 0
#define CAL_TRCO_NOK 0x3A, 0x40, 6 // 1 = NOK
#define CAL_TRCO_DONE 0x3A, 0x80, 7 // 0 = OK
#define CAL_SRCO_NOK 0x3B, 0x40, 6
#define CAL_SRCO_DONE 0x3B, 0x80, 7
// I2C Commands
#define RESET_DEFAULT 0x3C, 0x96
#define CALIBATE_RCO 0x3D, 0x96
// NF-Level
#define INDOORS 0x24
#define OUTDOORS 0x1C
// Global
const char HTTP_SNS_UNIT_KILOMETER[] PROGMEM = D_UNIT_KILOMETER;
// Load Settings Mask
#define SETREG00MASK 0x3E // For Power On
#define SETREG03MASK 0xF0 // For LCO and Disturber
// Http
const char HTTP_SNS_AS3935_ENERGY[] PROGMEM = "{s}" D_NAME_AS3935 " " D_AS3935_ENERGY " {m}%d{e}";
const char HTTP_SNS_AS3935_DISTANZ[] PROGMEM = "{s}" D_NAME_AS3935 " " D_AS3935_DISTANCE " {m}%u " D_UNIT_KILOMETER "{e}";
@ -66,15 +79,18 @@ const char HTTP_SNS_AS3935_DIST_ON[] PROGMEM = "{s}%s " D_AS3935_DISTURBER " {m}
const char HTTP_SNS_AS3935_DIST_OFF[] PROGMEM = "{s}%s " D_AS3935_DISTURBER " {m}" D_AS3935_OFF " {e}";
const char* const HTTP_SNS_AS3935_DISTURBER[] PROGMEM = {HTTP_SNS_AS3935_DIST_OFF, HTTP_SNS_AS3935_DIST_ON};
// http Messages
const char HTTP_SNS_AS3935_EMPTY[] PROGMEM = "{s}%s: " D_AS3935_NOMESS "{e}";
const char HTTP_SNS_AS3935_OUT[] PROGMEM = "{s}%s: " D_AS3935_OUT "{e}";
const char HTTP_SNS_AS3935_NOT[] PROGMEM = "{s}%s: " D_AS3935_NOT "{e}";
const char HTTP_SNS_AS3935_ABOVE[] PROGMEM = "{s}%s: " D_AS3935_ABOVE "{e}";
const char HTTP_SNS_AS3935_NOISE[] PROGMEM = "{s}%s: " D_AS3935_NOISE "{e}";
const char HTTP_SNS_AS3935_DISTURB[] PROGMEM = "{s}%s: " D_AS3935_DISTDET "{e}";
const char HTTP_SNS_AS3935_INTNOEV[] PROGMEM = "{s}%s: " D_AS3935_INTNOEV "{e}";
const char HTTP_SNS_AS3935_MSG[] PROGMEM = "{s}%s: " D_AS3935_LIGHT " " D_AS3935_APRX " %d " D_UNIT_KILOMETER " " D_AS3935_AWAY "{e}";
const char* const HTTP_SNS_AS3935_TABLE_1[] PROGMEM = { HTTP_SNS_AS3935_EMPTY, HTTP_SNS_AS3935_MSG, HTTP_SNS_AS3935_OUT, HTTP_SNS_AS3935_NOT, HTTP_SNS_AS3935_ABOVE, HTTP_SNS_AS3935_NOISE, HTTP_SNS_AS3935_DISTURB, HTTP_SNS_AS3935_INTNOEV };
const char HTTP_SNS_AS3935_EMPTY[] PROGMEM = "{s}%s " D_AS3935_NOMESS "{e}";
const char HTTP_SNS_AS3935_OUT[] PROGMEM = "{s}%s " D_AS3935_OUT "{e}";
const char HTTP_SNS_AS3935_NOT[] PROGMEM = "{s}%s " D_AS3935_NOT "{e}";
const char HTTP_SNS_AS3935_ABOVE[] PROGMEM = "{s}%s " D_AS3935_ABOVE "{e}";
const char HTTP_SNS_AS3935_NOISE[] PROGMEM = "{s}%s " D_AS3935_NOISE "{e}";
const char HTTP_SNS_AS3935_DISTURB[] PROGMEM = "{s}%s " D_AS3935_DISTDET "{e}";
const char HTTP_SNS_AS3935_INTNOEV[] PROGMEM = "{s}%s " D_AS3935_INTNOEV "{e}";
const char HTTP_SNS_AS3935_FLICKER[] PROGMEM = "{s}%s " D_AS3935_FLICKER "{e}";
const char HTTP_SNS_AS3935_POWEROFF[] PROGMEM = "{s}%s " D_AS3935_POWEROFF "{e}";
const char HTTP_SNS_AS3935_MSG[] PROGMEM = "{s}%s " D_AS3935_LIGHT " " D_AS3935_APRX " %d " D_UNIT_KILOMETER " " D_AS3935_AWAY "{e}";
const char* const HTTP_SNS_AS3935_TABLE_1[] PROGMEM = { HTTP_SNS_AS3935_EMPTY, HTTP_SNS_AS3935_MSG, HTTP_SNS_AS3935_OUT, HTTP_SNS_AS3935_NOT, HTTP_SNS_AS3935_ABOVE, HTTP_SNS_AS3935_NOISE, HTTP_SNS_AS3935_DISTURB, HTTP_SNS_AS3935_INTNOEV, HTTP_SNS_AS3935_FLICKER, HTTP_SNS_AS3935_POWEROFF };
// Json
const char JSON_SNS_AS3935_EVENTS[] PROGMEM = ",\"%s\":{\"" D_JSON_EVENT "\":%d,\"" D_JSON_DISTANCE "\":%d,\"" D_JSON_ENERGY "\":%u,\"" D_JSON_STAGE "\":%d}";
// Json Command
@ -84,13 +100,17 @@ const char* const S_JSON_AS3935_COMMAND_CAL[] PROGMEM = {"" D_AS3935_CAL_FAIL ""
const char S_JSON_AS3935_COMMAND_STRING[] PROGMEM = "{\"" D_NAME_AS3935 "\":{\"%s\":%s}}";
const char S_JSON_AS3935_COMMAND_NVALUE[] PROGMEM = "{\"" D_NAME_AS3935 "\":{\"%s\":%d}}";
const char S_JSON_AS3935_COMMAND_SETTINGS[] PROGMEM = "{\"" D_NAME_AS3935 "\":{\"Gain\":%s,\"NFfloor\":%d,\"uVrms\":%d,\"Tunecaps\":%d,\"MinNumLight\":%d,\"Rejektion\":%d,\"Wdthreshold\":%d,\"MinNFstage\":%d,\"NFAutoTime\":%d,\"DisturberAutoTime\":%d,\"Disturber\":%s,\"NFauto\":%s,\"Disturberauto\":%s,\"NFautomax\":%s,\"Mqttlightevent\":%s}}";
const char S_JSON_AS3935_COMMAND_SETTINGS[] PROGMEM = "{\"AS3935_Settings\":{\"Gain\":%s,\"NFfloor\":%d,\"uVrms\":%d,\"Tunecaps\":%d,\"MinNumLight\":%d,\"Rejektion\":%d,\"Wdthreshold\":%d,\"MinNFstage\":%d,\"NFAutoTime\":%d,\"DisturberAutoTime\":%d,\"Disturber\":%s,\"NFauto\":%s,\"Disturberauto\":%s,\"NFautomax\":%s,\"Mqttlightevent\":%s,\"Mqttnoirqevent\":%s}}";
const char kAS3935_Commands[] PROGMEM = "setnf|setminstage|setml|default|setgain|settunecaps|setrej|setwdth|disttime|nftime|disturber|autonf|autodisturber|autonfmax|mqttevent|settings|calibrate";
const char kAS3935_Commands[] PROGMEM = "power|setnf|setminstage|setml|default|setgain|settunecaps|setrej|setwdth|disttime|nftime|disturber|autonf|autodisturber|autonfmax|lightevent|noirqevent|settings|calibrate";
const uint8_t AS3935_VrmsIndoor[] PROGMEM = { 28, 45, 62, 78, 95, 112, 130, 146 };
const uint16_t AS3935_VrmsOutdoor[] PROGMEM = { 390, 630, 860, 1100, 1140, 1570, 1800, 2000 };
enum AS3935_Commands { // commands for Console
CMND_AS3935_POWER, // Power on/off the device (1 Bit)
CMND_AS3935_SET_NF, // Noise Floor Level, value from 0-7 (3 Bit)
CMND_AS3935_SET_MINNF, // Set Min Noise Floor Level when Autotune is active Value von 0-15
CMND_AS3935_SET_MINNF, // Set Min Noise Floor Level when Autotune is active Value from 0-15
CMND_AS3935_SET_MINLIGHT, // Minimum number of lightning 0=1/1=5/2=9/3=16 Lightnings
CMND_AS3935_SET_DEF, // set default for Sensor and Settings
CMND_AS3935_SET_GAIN, // Set Inddoor/Outdoor
@ -104,24 +124,25 @@ enum AS3935_Commands { // commands for Console
CMND_AS3935_DIST_AUTOTUNE, // Autotune Disturber on/off
CMND_AS3935_NF_ATUNE_BOTH, // Autotune over both Areas: INDOORS/OUDOORS
CMND_AS3935_MQTT_LIGHT_EVT, // mqtt only if lightning Irq
CMND_AS3935_MQTT_NO_IRQ_EVT, // suppress mqtt "IRQ with no Event"
CMND_AS3935_SETTINGS, // Json output of all settings
CMND_AS3935_CALIBRATE // caps autocalibrate
};
};
struct AS3935STRUCT
{
bool autodist_activ = false;
struct {
bool active = false;
bool http_count_start = false;
bool poweroff = false;
volatile bool detected = false;
volatile bool dispLCO = 0;
uint8_t icount = 0;
volatile bool dispLCO = false;
volatile uint8_t icount = 0;
uint8_t irq = 0;
uint8_t mqtt_irq = 0;
uint8_t http_irq = 0;
uint8_t http_count_start = 0;
uint8_t mqtt_event = 0;
uint8_t http_event = 0;
uint8_t http_time = 0;
uint8_t http_count = 0;
int16_t http_distance = 0;
int16_t distance = 0;
uint16_t http_timer = 0;
uint16_t http_count = 0;
uint16_t nftimer = 0;
uint16_t disttimer = 0;
uint32_t intensity = 0;
@ -129,16 +150,16 @@ struct AS3935STRUCT
volatile uint32_t pulse = 0;
} as3935_sensor;
uint8_t as3935_active = 0;
void ICACHE_RAM_ATTR AS3935Isr() {
void ICACHE_RAM_ATTR AS3935Isr(void) {
as3935_sensor.detected = true;
as3935_sensor.icount++;
}
// we have to store 5 Bytes in the eeprom. Register 8 is mapped to Byte 4
uint8_t AS3935ReadRegister(uint8_t reg, uint8_t mask, uint8_t shift) {
uint8_t data = I2cRead8(AS3935_ADDR, reg);
if (reg == 0x08) Settings.as3935_sensor_cfg[4] = data;
if (reg < 0x04) Settings.as3935_sensor_cfg[reg] = data;
if (reg <= 0x03) Settings.as3935_sensor_cfg[reg] = data;
return ((data & mask) >> shift);
}
@ -150,12 +171,12 @@ void AS3935WriteRegister(uint8_t reg, uint8_t mask, uint8_t shift, uint8_t data)
data |= currentReg;
I2cWrite8(AS3935_ADDR, reg, data);
if (reg == 0x08) Settings.as3935_sensor_cfg[4] = I2cRead8(AS3935_ADDR, reg);
if (reg < 0x04) Settings.as3935_sensor_cfg[reg] = I2cRead8(AS3935_ADDR, reg);
if (reg <= 0x03) Settings.as3935_sensor_cfg[reg] = I2cRead8(AS3935_ADDR, reg);
}
/********************************************************************************************/
// Autotune Caps
void ICACHE_RAM_ATTR AS3935CountFreq() {
void ICACHE_RAM_ATTR AS3935CountFreq(void) {
if (as3935_sensor.dispLCO)
as3935_sensor.pulse++;
}
@ -163,52 +184,161 @@ void ICACHE_RAM_ATTR AS3935CountFreq() {
bool AS3935AutoTuneCaps(uint8_t irqpin) {
int32_t maxtune = 17500; // there max 3.5 % tol
uint8_t besttune;
uint8_t oldvalue = AS3935GetTuneCaps();
AS3935WriteRegister(LCO_FDIV, 0); // Fdiv 16
delay(2);
for (uint8_t tune = 0; tune < 16; tune++) {
AS3935WriteRegister(TUNE_CAPS, tune);
delay(2);
AS3935WriteRegister(DISP_LCO,1);
AS3935WriteRegister(DISP_LCO, 1);
delay(1);
as3935_sensor.dispLCO = true;
as3935_sensor.pulse = 0;
attachInterrupt(digitalPinToInterrupt(irqpin), AS3935CountFreq, RISING);
delay(200); // 100ms callback not work accurat for fequ. measure
delay(50);
as3935_sensor.dispLCO = false;
detachInterrupt(irqpin);
AS3935WriteRegister(DISP_LCO,0);
int32_t currentfreq = 500000 - ((as3935_sensor.pulse * 5) * 16);
AS3935WriteRegister(DISP_LCO, 0);
int32_t currentfreq = 500000 - ((as3935_sensor.pulse * 20) * 16);
if(currentfreq < 0) currentfreq = -currentfreq;
if(maxtune > currentfreq) {
maxtune = currentfreq;
besttune = tune;
}
}
if (maxtune >= 17500) // max. 3.5%
if (maxtune >= 17500) { // max. 3.5%
AS3935SetTuneCaps(oldvalue);
return false;
}
AS3935SetTuneCaps(besttune);
return true;
}
/********************************************************************************************/
// functions
void AS3935CalibrateRCO() {
I2cWrite8(AS3935_ADDR, 0x3D, 0x96);
AS3935WriteRegister(DISP_TRCO, 1);
bool AS3935CalRCOResult(void) {
if(AS3935ReadRegister(CAL_SRCO_NOK) || AS3935ReadRegister(CAL_TRCO_NOK)) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("I2C: AS3935 Fatal Failure of TRCO or SRCO calibration"));
return false;
}
return true;
}
bool AS3935CalibrateRCO(void) {
detachInterrupt(Pin(GPIO_AS3935)); // Prevent AS3935Isr from RCO Calibration
I2cWrite8(AS3935_ADDR, CALIBATE_RCO); // Cal TRCO & SRCO
AS3935WriteRegister(DISP_TRCO, 1); // need for Power up
delay(2);
AS3935WriteRegister(DISP_TRCO, 0);
if(!AS3935CalRCOResult())
return false;
attachInterrupt(digitalPinToInterrupt(Pin(GPIO_AS3935)), AS3935Isr, RISING);
return true;
}
void AS3935Reset(void) {
I2cWrite8(AS3935_ADDR, RESET_DEFAULT);
delay(2);
}
void AS3935PwrDown(void) {
AS3935WriteRegister(PWR_REG ,1);
detachInterrupt(Pin(GPIO_AS3935));
as3935_sensor.poweroff = true;
as3935_sensor.mqtt_event = 9;
as3935_sensor.http_event = 9;
as3935_sensor.intensity = 0;
as3935_sensor.distance = 0;
}
void AS3935PwrUp(void) {
AS3935WriteRegister(PWR_REG ,0);
AS3935CalibrateRCO();
as3935_sensor.poweroff = false;
as3935_sensor.mqtt_event = 0;
as3935_sensor.http_event = 0;
}
uint8_t AS3935GetPwrStat(void) {
if (AS3935ReadRegister(PWR_REG))
return 0;
return 1;
}
uint8_t AS3935GetIRQ(void) {
delay(2);
return AS3935ReadRegister(IRQ_TBL);
}
uint8_t AS3935TranslIrq(uint8_t irq, uint8_t distance) {
switch(irq) {
case 0: return 7; // Interrupt with no IRQ
case 1: return 5; // Noise level too high
case 4: return 6; // Disturber detected
case 8:
if (distance == -1) return 2; // Lightning out of Distance
else if (distance == 0) return 3; // Distance cannot be determined
else if (distance == 1) return 4; // Storm is Overhead
else return 1; // Lightning with Distance detected
}
return 0; // Fix GCC 10.1 warning
}
uint8_t AS3935GetDistance(void) {
return AS3935ReadRegister(LGHT_DIST);
}
int16_t AS3935CalcDistance(void) {
uint8_t dist = AS3935GetDistance();
switch (dist) {
case 0x3F: return -1; // Out of Range
case 0x00: return 0; // Distance cannot be determined
case 0x01: return 1; // Storm is Overhead
default:
if (40 < dist) return 40; // limited because higher is not accurate
return dist;
}
}
uint32_t AS3935GetIntensity(void) {
uint32_t energy_raw = (AS3935ReadRegister(ENERGY_RAW_3) << 8);
energy_raw |= AS3935ReadRegister(ENERGY_RAW_2);
energy_raw <<= 8;
energy_raw |= AS3935ReadRegister(ENERGY_RAW_1);
return energy_raw;
}
uint8_t AS3935GetTuneCaps(void) {
return AS3935ReadRegister(TUNE_CAPS);
}
void AS3935SetTuneCaps(uint8_t tune) {
AS3935WriteRegister(TUNE_CAPS, tune);
delay(2);
AS3935CalibrateRCO();
}
uint8_t AS3935GetDisturber(void) {
return AS3935ReadRegister(DISTURBER);
}
void AS3935SetDisturber(uint8_t stat) {
AS3935WriteRegister(DISTURBER, stat);
}
uint8_t AS3935GetMinLights(void) {
return AS3935ReadRegister(MIN_NUM_LIGH);
}
void AS3935SetMinLights(uint8_t stat) {
AS3935WriteRegister(MIN_NUM_LIGH, stat);
}
uint8_t AS3935TransMinLights(uint8_t min_lights) {
if (5 > min_lights) {
return 0;
} else if (9 > min_lights) {
return 1;
} else if (16 > min_lights) {
return 2;
} else {
return 3;
}
if (5 > min_lights) return 0;
else if (9 > min_lights) return 1;
else if (16 > min_lights) return 2;
else return 3;
}
uint8_t AS3935TranslMinLightsInt(uint8_t min_lights) {
@ -221,146 +351,7 @@ uint8_t AS3935TranslMinLightsInt(uint8_t min_lights) {
return 0; // Fix GCC 10.1 warning
}
uint8_t AS3935TranslIrq(uint8_t irq, uint8_t distance) {
switch(irq) {
case 0: return 7; // Interrupt with no IRQ
case 1: return 5; // Noise level too high
case 4: return 6; // Disturber detected
case 8:
if (distance == -1) return 2; // Lightning out of Distance
else if (distance == 0) return 3; // Distance cannot be determined
else if (distance == 1) return 4; // Storm is Overhead
else return 1; // Lightning with Distance detected
}
return 0; // Fix GCC 10.1 warning
}
void AS3935CalcVrmsLevel(uint16_t &vrms, uint8_t &stage)
{
uint8_t room = AS3935GetGain();
uint8_t nflev = AS3935GetNoiseFloor();
if (room == 0x24)
{
switch (nflev){
case 0x00:
vrms = 28;
break;
case 0x01:
vrms = 45;
break;
case 0x02:
vrms = 62;
break;
case 0x03:
vrms = 78;
break;
case 0x04:
vrms = 95;
break;
case 0x05:
vrms = 112;
break;
case 0x06:
vrms = 130;
break;
case 0x07:
vrms = 146;
break;
}
stage = nflev;
}
else
{
switch (nflev)
{
case 0x00:
vrms = 390;
break;
case 0x01:
vrms = 630;
break;
case 0x02:
vrms = 860;
break;
case 0x03:
vrms = 1100;
break;
case 0x04:
vrms = 1140;
break;
case 0x05:
vrms = 1570;
break;
case 0x06:
vrms = 1800;
break;
case 0x07:
vrms = 2000;
break;
}
stage = nflev + 8;
}
}
/********************************************************************************************/
uint8_t AS3935GetIRQ() {
delay(2);
return AS3935ReadRegister(IRQ_TBL);
}
uint8_t AS3935GetDistance() {
return AS3935ReadRegister(LGHT_DIST);
}
int16_t AS3935CalcDistance() {
uint8_t dist = AS3935GetDistance();
switch (dist) {
case 0x3F: return -1; // Out of Range
case 0x01: return 1; // Storm is Overhead
case 0x00: return 0; // Distance cannot be determined
default:
if (40 < dist){
return 40;// limited because higher is not accurate
}
return dist;
}
}
uint32_t AS3935GetIntensity() {
uint32_t nrgy_raw = (AS3935ReadRegister(ENERGY_RAW_3) << 8);
nrgy_raw |= AS3935ReadRegister(ENERGY_RAW_2);
nrgy_raw <<= 8;
nrgy_raw |= AS3935ReadRegister(ENERGY_RAW_1);
return nrgy_raw;
}
uint8_t AS3935GetTuneCaps() {
return AS3935ReadRegister(TUNE_CAPS);
}
void AS3935SetTuneCaps(uint8_t tune) {
AS3935WriteRegister(TUNE_CAPS, tune);
delay(2);
AS3935CalibrateRCO();
}
uint8_t AS3935GetDisturber() {
return AS3935ReadRegister(DISTURBER);
}
void AS3935SetDisturber(uint8_t stat) {
AS3935WriteRegister(DISTURBER, stat);
}
uint8_t AS3935GetMinLights() {
return AS3935ReadRegister(MIN_NUM_LIGH);
}
void AS3935SetMinLights(uint8_t stat) {
AS3935WriteRegister(MIN_NUM_LIGH, stat);
}
uint8_t AS3935GetNoiseFloor() {
uint8_t AS3935GetNoiseFloor(void) {
return AS3935ReadRegister(NF_LEVEL);
}
@ -368,7 +359,7 @@ void AS3935SetNoiseFloor(uint8_t noise) {
AS3935WriteRegister(NF_LEVEL , noise);
}
uint8_t AS3935GetGain() {
uint8_t AS3935GetGain(void) {
if (AS3935ReadRegister(AFE_GB) == OUTDOORS)
return OUTDOORS;
return INDOORS;
@ -378,13 +369,25 @@ void AS3935SetGain(uint8_t room) {
AS3935WriteRegister(AFE_GB, room);
}
uint8_t AS3935GetGainInt() {
uint8_t AS3935GetGainInt(void) {
if (AS3935ReadRegister(AFE_GB) == OUTDOORS)
return 1;
return 0;
return 1;
return 0;
}
uint8_t AS3935GetSpikeRejection() {
void AS3935CalcVrmsLevel(uint16_t &vrms, uint8_t &stage) {
uint8_t room = AS3935GetGain();
uint8_t nflev = AS3935GetNoiseFloor();
if (room == INDOORS) {
vrms = pgm_read_byte(AS3935_VrmsIndoor + nflev);
stage = nflev;
} else {
vrms = pgm_read_word(AS3935_VrmsOutdoor + nflev);
stage = nflev + 8;
}
}
uint8_t AS3935GetSpikeRejection(void) {
return AS3935ReadRegister(SPIKE_REJECT);
}
@ -392,7 +395,7 @@ void AS3935SetSpikeRejection(uint8_t rej) {
AS3935WriteRegister(SPIKE_REJECT, rej);
}
uint8_t AS3935GetWdth() {
uint8_t AS3935GetWdth(void) {
return AS3935ReadRegister(WDTH);
}
@ -400,17 +403,15 @@ void AS3935SetWdth(uint8_t wdth) {
AS3935WriteRegister(WDTH, wdth);
}
bool AS3935AutoTune(){
bool AS3935AutoTune(void) {
detachInterrupt(Pin(GPIO_AS3935));
bool result = AS3935AutoTuneCaps(Pin(GPIO_AS3935));
attachInterrupt(digitalPinToInterrupt(Pin(GPIO_AS3935)), AS3935Isr, RISING);
return result;
}
/********************************************************************************************/
// Noise Floor autofunctions
bool AS3935LowerNoiseFloor() {
uint8_t noise = AS3935GetNoiseFloor();
bool AS3935LowerNoiseFloor(void) {
uint16_t vrms;
uint8_t stage;
AS3935CalcVrmsLevel(vrms, stage);
@ -421,6 +422,7 @@ bool AS3935LowerNoiseFloor() {
return true;
}
}
uint8_t noise = AS3935GetNoiseFloor();
if (0 < noise && stage > Settings.as3935_parameter.nf_autotune_min) {
noise--;
AS3935SetNoiseFloor(noise);
@ -429,7 +431,7 @@ bool AS3935LowerNoiseFloor() {
return false;
}
bool AS3935RaiseNoiseFloor() {
bool AS3935RaiseNoiseFloor(void) {
uint8_t noise = AS3935GetNoiseFloor();
uint8_t room = AS3935GetGain();
if (Settings.as3935_functions.nf_autotune_both) {
@ -449,22 +451,25 @@ bool AS3935RaiseNoiseFloor() {
/********************************************************************************************/
// init functions
bool AS3935SetDefault() {
I2cWrite8(AS3935_ADDR, 0x3C, 0x96); // Set default
delay(2);
bool AS3935SetDefault(void) {
AS3935Reset();
AS3935SetDisturber(1); // Disturber on by default
AS3935SetNoiseFloor(7); // NF High on by default
Settings.as3935_sensor_cfg[0] = I2cRead8(AS3935_ADDR, 0x00);
Settings.as3935_sensor_cfg[1] = I2cRead8(AS3935_ADDR, 0x01);
Settings.as3935_sensor_cfg[2] = I2cRead8(AS3935_ADDR, 0x02);
Settings.as3935_sensor_cfg[3] = I2cRead8(AS3935_ADDR, 0x03);
Settings.as3935_sensor_cfg[4] = I2cRead8(AS3935_ADDR, 0x08);
// set all eeprom functions and values to default
Settings.as3935_functions.data = 0x00;
Settings.as3935_parameter.nf_autotune_min = 0x00;
Settings.as3935_parameter.nf_autotune_time = 4;
Settings.as3935_parameter.dist_autotune_time = 1;
return true;
}
void AS3935InitSettings() {
if(Settings.as3935_functions.nf_autotune){
void AS3935InitSettings(void) {
if(Settings.as3935_functions.nf_autotune) {
if(Settings.as3935_parameter.nf_autotune_min) {
if (Settings.as3935_parameter.nf_autotune_min > 7) {
AS3935SetGain(OUTDOORS);
@ -475,114 +480,137 @@ void AS3935InitSettings() {
}
}
}
I2cWrite8(AS3935_ADDR, 0x00, Settings.as3935_sensor_cfg[0]);
I2cWrite8(AS3935_ADDR, 0x00, Settings.as3935_sensor_cfg[0] & SETREG00MASK);
I2cWrite8(AS3935_ADDR, 0x01, Settings.as3935_sensor_cfg[1]);
I2cWrite8(AS3935_ADDR, 0x02, Settings.as3935_sensor_cfg[2]);
I2cWrite8(AS3935_ADDR, 0x03, Settings.as3935_sensor_cfg[3]);
I2cWrite8(AS3935_ADDR, 0x03, Settings.as3935_sensor_cfg[3] & SETREG03MASK);
I2cWrite8(AS3935_ADDR, 0x08, Settings.as3935_sensor_cfg[4]);
delay(2);
}
void AS3935Setup(void) {
bool AS3935Setup(void) {
if (Settings.as3935_sensor_cfg[0] == 0x00) {
AS3935SetDefault();
} else {
AS3935InitSettings();
}
AS3935CalibrateRCO();
return AS3935CalibrateRCO();
}
bool AS3935init() {
uint8_t ret = I2cRead8(AS3935_ADDR, 0x00);
if(INDOORS == ret || OUTDOORS == ret) // 0x24
bool AS3935init(void) {
AS3935Reset();
uint8_t afe_gb = I2cRead8(AS3935_ADDR, 0x00) & SETREG00MASK;
if(INDOORS == afe_gb)
return true;
return false;
}
void AS3935Detect(void) {
if (I2cActive(AS3935_ADDR)) return;
if (AS3935init())
{
if (!I2cSetDevice(AS3935_ADDR)) return;
if (AS3935init()) {
I2cSetActiveFound(AS3935_ADDR, D_NAME_AS3935);
pinMode(Pin(GPIO_AS3935), INPUT);
attachInterrupt(digitalPinToInterrupt(Pin(GPIO_AS3935)), AS3935Isr, RISING);
AS3935Setup();
as3935_active = 1;
if (PinUsed(GPIO_AS3935)) {
pinMode(Pin(GPIO_AS3935), INPUT);
if (!AS3935Setup()) return;
as3935_sensor.active = true;
} else {
AddLog_P2(LOG_LEVEL_INFO, PSTR("I2C: AS3935 GPIO Pin not defined!"));
}
}
}
void AS3935EverySecond() {
if (as3935_sensor.detected) {
as3935_sensor.irq = AS3935GetIRQ(); // 1 =Noise, 4 = Disturber, 8 = storm
switch (as3935_sensor.irq) {
case 1:
if (Settings.as3935_functions.nf_autotune) {
if (AS3935RaiseNoiseFloor()) as3935_sensor.nftimer = 0;
void AS3935EverySecond(void) {
if (!as3935_sensor.poweroff) { // Power Off
if (as3935_sensor.detected) {
as3935_sensor.detected = false;
as3935_sensor.irq = AS3935GetIRQ(); // 1 = Noise, 4 = Disturber, 8 = storm
if (10 > as3935_sensor.icount) {
switch (as3935_sensor.irq) {
case 1:
if (Settings.as3935_functions.nf_autotune) {
if (AS3935RaiseNoiseFloor())
as3935_sensor.nftimer = 0;
}
break;
case 4:
if (Settings.as3935_functions.dist_autotune) {
AS3935SetDisturber(1);
}
break;
case 8:
as3935_sensor.intensity = AS3935GetIntensity();
as3935_sensor.distance = AS3935CalcDistance();
as3935_sensor.http_intensity = as3935_sensor.intensity;
as3935_sensor.http_distance = as3935_sensor.distance;
break;
}
break;
case 4:
if (Settings.as3935_functions.dist_autotune) {
AS3935SetDisturber(1);
as3935_sensor.autodist_activ = true;
}
break;
case 8:
as3935_sensor.intensity = AS3935GetIntensity();
as3935_sensor.distance = AS3935CalcDistance();
as3935_sensor.http_intensity = as3935_sensor.intensity;
as3935_sensor.http_distance = as3935_sensor.distance;
break;
}
// http show
as3935_sensor.http_irq = AS3935TranslIrq(as3935_sensor.irq, as3935_sensor.distance);
// mqtt publish
as3935_sensor.mqtt_irq = as3935_sensor.http_irq;
switch (as3935_sensor.mqtt_irq) {
// http show
as3935_sensor.http_event = AS3935TranslIrq(as3935_sensor.irq, as3935_sensor.distance);
} else {
as3935_sensor.http_event = 8; // flicker detected
}
// mqtt publish
as3935_sensor.mqtt_event = as3935_sensor.http_event;
switch (as3935_sensor.mqtt_event) {
case 5:
case 6:
if (!Settings.as3935_functions.mqtt_only_Light_Event) {
if (!Settings.as3935_functions.mqtt_only_Light_Event) {
MqttPublishSensor();
as3935_sensor.http_timer = 10;
}
break;
default:
as3935_sensor.http_timer = 60;
as3935_sensor.http_time = 10;
}
break;
case 7:
if (!Settings.as3935_functions.suppress_irq_no_Event) {
MqttPublishSensor();
as3935_sensor.http_time = 10;
}
break;
default:
as3935_sensor.http_time = 30;
MqttPublishSensor();
}
// clear mqtt events for Teleperiod
as3935_sensor.intensity = 0;
as3935_sensor.distance = 0;
as3935_sensor.mqtt_irq = 0;
// start http times
as3935_sensor.http_count_start = 1;
as3935_sensor.http_count = 0;
as3935_sensor.icount++; // Int counter
as3935_sensor.detected = false;
}
}
if (as3935_sensor.http_count_start) as3935_sensor.http_count++;
// clear Http
if (as3935_sensor.http_count > as3935_sensor.http_timer) {
as3935_sensor.http_count_start = 0;
as3935_sensor.http_intensity = 0;
as3935_sensor.http_distance = 0;
as3935_sensor.http_irq = 0;
}
// Noise Floor Autotune function
if (Settings.as3935_functions.nf_autotune) {
as3935_sensor.nftimer++;
if (as3935_sensor.nftimer > Settings.as3935_parameter.nf_autotune_time * 60) {
AS3935LowerNoiseFloor();
as3935_sensor.nftimer = 0;
as3935_sensor.irq = 0;
// clear mqtt events for Teleperiod
as3935_sensor.intensity = 0;
as3935_sensor.distance = 0;
as3935_sensor.mqtt_event = 0;
// start http times
as3935_sensor.http_count_start = true;
as3935_sensor.http_count = 0;
}
}
// Disturber auto function
if (Settings.as3935_functions.dist_autotune) {
if (as3935_sensor.autodist_activ) as3935_sensor.disttimer++;
if (as3935_sensor.disttimer >= Settings.as3935_parameter.dist_autotune_time * 60) {
AS3935SetDisturber(0);
as3935_sensor.disttimer = 0;
as3935_sensor.autodist_activ = false;
as3935_sensor.icount = 0;
// count http times
if (as3935_sensor.http_count_start)
as3935_sensor.http_count++;
// clear Http Event
if (as3935_sensor.http_count > as3935_sensor.http_time) {
as3935_sensor.http_count_start = false;
as3935_sensor.http_intensity = 0;
as3935_sensor.http_distance = 0;
as3935_sensor.http_event = 0;
}
// Noise Floor Autotune function
if (Settings.as3935_functions.nf_autotune) {
as3935_sensor.nftimer++;
if (as3935_sensor.nftimer > Settings.as3935_parameter.nf_autotune_time * 60) {
AS3935LowerNoiseFloor();
as3935_sensor.nftimer = 0;
}
}
// Disturber auto function
if (Settings.as3935_functions.dist_autotune) {
if (AS3935GetDisturber()) {
as3935_sensor.disttimer++;
}
if (as3935_sensor.disttimer >= Settings.as3935_parameter.dist_autotune_time * 60) {
AS3935SetDisturber(0);
as3935_sensor.disttimer = 0;
}
}
}
}
@ -593,6 +621,16 @@ bool AS3935Cmd(void) {
if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_NAME_AS3935), name_len)) {
uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + name_len, kAS3935_Commands);
switch (command_code) {
case CMND_AS3935_POWER:
if (XdrvMailbox.data_len) {
if (!XdrvMailbox.payload) {
AS3935PwrDown();
} else {
AS3935PwrUp();
}
}
Response_P(S_JSON_AS3935_COMMAND_STRING, command, S_JSON_AS3935_COMMAND_ONOFF[AS3935GetPwrStat()]);
break;
case CMND_AS3935_SET_NF:
if (XdrvMailbox.data_len) {
if (15 >= XdrvMailbox.payload) {
@ -712,6 +750,14 @@ bool AS3935Cmd(void) {
}
Response_P(S_JSON_AS3935_COMMAND_STRING, command, S_JSON_AS3935_COMMAND_ONOFF[Settings.as3935_functions.mqtt_only_Light_Event]);
break;
case CMND_AS3935_MQTT_NO_IRQ_EVT:
if (XdrvMailbox.data_len) {
if (2 > XdrvMailbox.payload) {
Settings.as3935_functions.suppress_irq_no_Event = XdrvMailbox.payload;
}
}
Response_P(S_JSON_AS3935_COMMAND_STRING, command, S_JSON_AS3935_COMMAND_ONOFF[Settings.as3935_functions.suppress_irq_no_Event]);
break;
case CMND_AS3935_SETTINGS: {
if (!XdrvMailbox.data_len) {
uint8_t gain = AS3935GetGainInt();
@ -719,30 +765,31 @@ bool AS3935Cmd(void) {
uint8_t stage;
AS3935CalcVrmsLevel(vrms, stage);
uint8_t nf_floor = AS3935GetNoiseFloor();
uint8_t min_nf = Settings.as3935_parameter.nf_autotune_min;
uint8_t tunecaps = AS3935GetTuneCaps();
uint8_t minnumlight = AS3935TranslMinLightsInt(AS3935GetMinLights());
uint8_t disturber = AS3935GetDisturber();
uint8_t reinj = AS3935GetSpikeRejection();
uint8_t wdth = AS3935GetWdth();
uint8_t min_nf = Settings.as3935_parameter.nf_autotune_min;
uint8_t nf_time = Settings.as3935_parameter.nf_autotune_time;
uint8_t nfauto = Settings.as3935_functions.nf_autotune;
uint8_t distauto = Settings.as3935_functions.dist_autotune;
uint8_t nfautomax = Settings.as3935_functions.nf_autotune_both;
uint8_t distauto = Settings.as3935_functions.dist_autotune;
uint8_t jsonlight = Settings.as3935_functions.mqtt_only_Light_Event;
uint8_t nf_time = Settings.as3935_parameter.nf_autotune_time;
uint8_t dist_time =Settings.as3935_parameter.dist_autotune_time;
Response_P(S_JSON_AS3935_COMMAND_SETTINGS, S_JSON_AS3935_COMMAND_GAIN[gain], nf_floor, vrms, tunecaps, minnumlight, reinj, wdth, min_nf, nf_time, dist_time, S_JSON_AS3935_COMMAND_ONOFF[disturber], S_JSON_AS3935_COMMAND_ONOFF[nfauto], S_JSON_AS3935_COMMAND_ONOFF[distauto], S_JSON_AS3935_COMMAND_ONOFF[nfautomax], S_JSON_AS3935_COMMAND_ONOFF[jsonlight]);
uint8_t jsonirq = Settings.as3935_functions.suppress_irq_no_Event;
uint8_t dist_time = Settings.as3935_parameter.dist_autotune_time;
Response_P(S_JSON_AS3935_COMMAND_SETTINGS, S_JSON_AS3935_COMMAND_GAIN[gain], nf_floor, vrms, tunecaps, minnumlight, reinj, wdth, min_nf, nf_time, dist_time, S_JSON_AS3935_COMMAND_ONOFF[disturber], S_JSON_AS3935_COMMAND_ONOFF[nfauto], S_JSON_AS3935_COMMAND_ONOFF[distauto], S_JSON_AS3935_COMMAND_ONOFF[nfautomax], S_JSON_AS3935_COMMAND_ONOFF[jsonlight], S_JSON_AS3935_COMMAND_ONOFF[jsonirq]);
}
}
break;
case CMND_AS3935_CALIBRATE: {
bool calreslt;
if (!XdrvMailbox.data_len) calreslt = AS3935AutoTune();
Response_P(S_JSON_AS3935_COMMAND_NVALUE, S_JSON_AS3935_COMMAND_CAL[calreslt], AS3935GetTuneCaps());
}
break;
default:
return false;
break;
case CMND_AS3935_CALIBRATE: {
bool calreslt;
if (!XdrvMailbox.data_len) calreslt = AS3935AutoTune();
Response_P(S_JSON_AS3935_COMMAND_NVALUE, S_JSON_AS3935_COMMAND_CAL[calreslt], AS3935GetTuneCaps());
}
break;
default:
return false;
}
return true;
} else {
@ -750,13 +797,12 @@ bool AS3935Cmd(void) {
}
}
void AH3935Show(bool json)
{
void AH3935Show(bool json) {
if (json) {
uint16_t vrms;
uint8_t stage;
AS3935CalcVrmsLevel(vrms, stage);
ResponseAppend_P(JSON_SNS_AS3935_EVENTS, D_SENSOR_AS3935, as3935_sensor.mqtt_irq, as3935_sensor.distance, as3935_sensor.intensity, stage);
ResponseAppend_P(JSON_SNS_AS3935_EVENTS, D_SENSOR_AS3935, as3935_sensor.mqtt_event, as3935_sensor.distance, as3935_sensor.intensity, stage);
#ifdef USE_WEBSERVER
} else {
uint8_t gain = AS3935GetGainInt();
@ -765,12 +811,13 @@ void AH3935Show(bool json)
uint8_t stage;
AS3935CalcVrmsLevel(vrms, stage);
WSContentSend_PD(HTTP_SNS_AS3935_TABLE_1[as3935_sensor.http_irq], D_NAME_AS3935, as3935_sensor.http_distance);
WSContentSend_PD(HTTP_SNS_AS3935_TABLE_1[as3935_sensor.http_event], D_NAME_AS3935, as3935_sensor.http_distance);
WSContentSend_PD(HTTP_SNS_AS3935_DISTANZ, as3935_sensor.http_distance);
WSContentSend_PD(HTTP_SNS_AS3935_ENERGY, as3935_sensor.http_intensity);
WSContentSend_PD(HTTP_SNS_AS3935_GAIN[gain], D_NAME_AS3935);
WSContentSend_PD(HTTP_SNS_AS3935_DISTURBER[disturber], D_NAME_AS3935);
WSContentSend_PD(HTTP_SNS_AS3935_VRMS, vrms, stage);
#endif // USE_WEBSERVER
}
}
@ -779,16 +826,13 @@ void AH3935Show(bool json)
* Interface
\*********************************************************************************************/
bool Xsns67(uint8_t function)
{
bool Xsns67(uint8_t function) {
if (!I2cEnabled(XI2C_48)) { return false; }
bool result = false;
if (FUNC_INIT == function) {
AS3935Detect();
}
else if (as3935_active) {
else if (as3935_sensor.active) {
switch (function) {
case FUNC_EVERY_SECOND:
AS3935EverySecond();

View File

@ -155,8 +155,14 @@ a_setoption = [[
"Enable zerocross dimmer on PWM DIMMER",
"Remove ZbReceived form JSON message",
"Add the source endpoint as suffix to attributes",
"","","","",
"","","","",
"Baud rate for Teleinfo communication (0 = 1200 or 1 = 9600)",
"TLS mode",
"Disable all MQTT retained messages",
"Enable White blend mode",
"Create a virtual White ColorTemp for RGBW lights",
"Select virtual White as (0) Warm or (1) Cold",
"Enable Teleinfo telemetry into Tasmota Energy MQTT (0) or Teleinfo only (1)",
"Force gen1 Alexa mode",
"","","",""
],[
"","","","",
@ -220,9 +226,9 @@ a_features = [[
"USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075",
"USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM",
"USE_HP303B","USE_TCP_BRIDGE","USE_TELEINFO","USE_LMT01",
"USE_PROMETHEUS","USE_IEM3000","USE_DYP","",
"USE_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)
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))