mirror of
https://github.com/arendst/Tasmota.git
synced 2025-07-23 18:56:38 +00:00
Merge branch 'development' into patch-1
This commit is contained in:
commit
c9715fa5b2
10
BUILDS.md
10
BUILDS.md
@ -10,6 +10,7 @@
|
||||
| USE_MQTT_TLS_CA_CERT | - | - | - | - | - | - | - |
|
||||
| USE_MQTT_AWS_IOT | - | - | - | - | - | - | - |
|
||||
| USE_4K_RSA | - | - | - | - | - | - | - |
|
||||
| USE_TELEGRAM | - | - | - | - | - | - | - |
|
||||
| USE_KNX | - | - | - | x | - | - | - |
|
||||
| USE_WEBSERVER | x | x | x | x | x | x | x |
|
||||
| USE_JAVASCRIPT_ES6 | - | - | - | - | - | - | - |
|
||||
@ -69,7 +70,9 @@
|
||||
| USE_DDSU666 | - | - | - | - | x | - | - |
|
||||
| USE_SOLAX_X1 | - | - | - | - | - | - | - |
|
||||
| USE_LE01MR | - | - | - | - | - | - | - |
|
||||
| USE_BL0940 | - | x | x | x | x | - | - |
|
||||
| USE_TELEINFO | - | - | - | - | - | - | - |
|
||||
| USE_IEM3000 | - | - | - | - | - | - | - |
|
||||
| | | | | | | | |
|
||||
| USE_ADC_VCC | x | x | - | - | - | x | - |
|
||||
| USE_COUNTER | - | - | x | x | x | - | x |
|
||||
@ -126,6 +129,7 @@
|
||||
| USE_VEML6075 | - | - | - | - | - | - | - |
|
||||
| USE_VEML7700 | - | - | - | - | - | - | - |
|
||||
| USE_MCP9808 | - | - | - | - | - | - | - |
|
||||
| USE_HP303B | - | - | - | - | - | - | - |
|
||||
| | | | | | | | |
|
||||
| Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks
|
||||
| USE_SPI | - | - | - | - | - | - | x |
|
||||
@ -143,8 +147,9 @@
|
||||
| USE_GPS | - | - | - | - | - | - | - |
|
||||
| USE_HM10 | - | - | - | - | x | - | - |
|
||||
| USE_HRXL | - | - | - | - | x | - | - |
|
||||
| USE_TASMOTA_SLAVE | - | - | - | - | - | - | - |
|
||||
| USE_TASMOTA_CLIENT | - | - | - | - | - | - | - |
|
||||
| USE_OPENTHERM | - | - | - | - | - | - | - |
|
||||
| USE_TCP_BRIDGE | - | - | - | - | - | - | - | zbbridge
|
||||
| | | | | | | | |
|
||||
| USE_NRF24 | - | - | - | - | - | - | - |
|
||||
| USE_MIBLE | - | - | - | - | - | - | - |
|
||||
@ -157,6 +162,7 @@
|
||||
| USE_IR_REMOTE_FULL | - | - | - | - | - | x | - | Enable ALL protocols
|
||||
| | | | | | | | |
|
||||
| USE_SR04 | - | - | - | - | x | - | - |
|
||||
| USE_DYP | - | - | - | - | - | - | - |
|
||||
| USE_TM1638 | - | - | - | - | x | - | - |
|
||||
| USE_HX711 | - | - | - | - | x | - | - |
|
||||
| USE_TX2x_WIND_SENSOR | - | - | - | - | - | - | - |
|
||||
@ -186,3 +192,5 @@
|
||||
| USE_MI_ESP32 | - | - | - | - | - | - | - | - |
|
||||
| USE_WEBCAM | - | - | - | - | - | - | - | x |
|
||||
| USE_ETHERNET | - | - | - | - | - | - | - | - |
|
||||
| USE_I2S_AUDIO | - | - | - | - | - | - | - | - |
|
||||
| USE_TTGO_WATCH | - | - | - | - | - | - | - | - |
|
||||
|
@ -80,4 +80,4 @@ Module | LCode | Description
|
||||
74 Sonoff D1 | x | Sonoff D1 Wifi and RF Dimmer
|
||||
75 Sonoff ZbBridge | x | Sonoff Zigbee bridge
|
||||
|
||||
Over 1400 additional devices are supported using [templates](TEMPLATES.md).
|
||||
Over 1500 additional devices are supported using [templates](TEMPLATES.md).
|
||||
|
@ -21,7 +21,7 @@ While fallback or downgrading is common practice it was never supported due to S
|
||||
|
||||
## Supported Core versions
|
||||
|
||||
This release will be supported from ESP8266/Arduino library Core version **2.7.2.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
|
||||
This release will be supported from ESP8266/Arduino library Core version **2.7.4.1** due to reported security and stability issues on previous Core version. This will also support gzipped binaries.
|
||||
|
||||
Support of Core versions before 2.7.1 has been removed.
|
||||
|
||||
@ -35,7 +35,7 @@ For initial configuration this release supports Webserver based **WifiManager**
|
||||
|
||||
## Provided Binary Downloads
|
||||
|
||||
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.2.1**.
|
||||
The following binary downloads have been compiled with ESP8266/Arduino library core version **2.7.4.1**.
|
||||
|
||||
- **tasmota.bin** = The Tasmota version with most drivers. **RECOMMENDED RELEASE BINARY**
|
||||
- **tasmota-BG.bin** to **tasmota-TW.bin** = The Tasmota version in different languages.
|
||||
@ -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
|
||||
|
452
TEMPLATES.md
452
TEMPLATES.md
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
@ -519,4 +519,5 @@ void VButton::xdrawButton(bool inverted) {
|
||||
wr_redir=0;
|
||||
}
|
||||
|
||||
|
||||
/* END OF 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);
|
||||
};
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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
39
platformio_tasmota32.ini
Normal 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
|
@ -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)
|
||||
|
@ -1,440 +0,0 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include <Arduino.h>
|
||||
#include "ets_sys.h"
|
||||
#include <atomic>
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
||||
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
||||
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
||||
// Maximum servicing time for any single IRQ
|
||||
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
||||
// The latency between in-ISR rearming of the timer and the earliest firing
|
||||
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
||||
// The SDK and hardware take some time to actually get to our NMI code
|
||||
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
||||
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
||||
|
||||
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
||||
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
||||
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
||||
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
||||
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
||||
|
||||
// Waveform generator can create tones, PWM, and servos
|
||||
typedef struct {
|
||||
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
||||
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
||||
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
||||
int32_t adjDutyCcys; // Temporary correction for next period
|
||||
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
||||
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
||||
WaveformMode mode;
|
||||
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
||||
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
||||
} Waveform;
|
||||
|
||||
namespace {
|
||||
|
||||
static struct {
|
||||
Waveform pins[17]; // State of all possible pins
|
||||
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||
|
||||
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
||||
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
||||
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
||||
|
||||
uint32_t(*timer1CB)() = nullptr;
|
||||
|
||||
bool timer1Running = false;
|
||||
|
||||
uint32_t nextEventCcy;
|
||||
} waveform;
|
||||
|
||||
}
|
||||
|
||||
// Interrupt on/off control
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
||||
|
||||
// Non-speed critical bits
|
||||
#pragma GCC optimize ("Os")
|
||||
|
||||
static void initTimer() {
|
||||
timer1_disable();
|
||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||
waveform.timer1Running = true;
|
||||
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
||||
}
|
||||
|
||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||
timer1_disable();
|
||||
timer1_isr_init();
|
||||
waveform.timer1Running = false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Set a callback. Pass in NULL to stop it
|
||||
void setTimer1Callback(uint32_t (*fn)()) {
|
||||
waveform.timer1CB = fn;
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
if (!waveform.timer1Running && fn) {
|
||||
initTimer();
|
||||
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
|
||||
deinitTimer();
|
||||
}
|
||||
}
|
||||
|
||||
int startWaveform(uint8_t pin, uint32_t highUS, uint32_t lowUS,
|
||||
uint32_t runTimeUS, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||
return startWaveformClockCycles(pin,
|
||||
microsecondsToClockCycles(highUS), microsecondsToClockCycles(lowUS),
|
||||
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
|
||||
}
|
||||
|
||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||
// first, then it will immediately begin.
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
|
||||
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
|
||||
uint32_t periodCcys = highCcys + lowCcys;
|
||||
if (periodCcys < MAXIRQTICKSCCYS) {
|
||||
if (!highCcys) {
|
||||
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
else if (!lowCcys) {
|
||||
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||
}
|
||||
}
|
||||
// sanity checks, including mixed signed/unsigned arithmetic safety
|
||||
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
|
||||
static_cast<int32_t>(periodCcys) <= 0 ||
|
||||
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
|
||||
return false;
|
||||
}
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
wave.dutyCcys = highCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
wave.periodCcys = periodCcys;
|
||||
wave.autoPwm = autoPwm;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (!(waveform.enabled & pinBit)) {
|
||||
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
|
||||
wave.nextPeriodCcy = phaseOffsetCcys;
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
|
||||
wave.mode = WaveformMode::INIT;
|
||||
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
|
||||
if (!wave.dutyCcys) {
|
||||
// If initially at zero duty cycle, force GPIO off
|
||||
if (pin == 16) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
if (!waveform.timer1Running) {
|
||||
initTimer();
|
||||
}
|
||||
else if (T1V > IRQLATENCYCCYS) {
|
||||
// Must not interfere if Timer is due shortly
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
}
|
||||
else {
|
||||
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
|
||||
if (runTimeCcys) {
|
||||
wave.mode = WaveformMode::UPDATEEXPIRY;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
waveform.toSetBits = 1UL << pin;
|
||||
}
|
||||
}
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
while (waveform.toSetBits) {
|
||||
delay(0); // Wait for waveform to update
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stops a waveform on a pin
|
||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
||||
// Can't possibly need to stop anything if there is no timer active
|
||||
if (!waveform.timer1Running) {
|
||||
return false;
|
||||
}
|
||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
if (waveform.enabled & pinBit) {
|
||||
waveform.toDisableBits = 1UL << pin;
|
||||
std::atomic_thread_fence(std::memory_order_release);
|
||||
// Must not interfere if Timer is due shortly
|
||||
if (T1V > IRQLATENCYCCYS) {
|
||||
timer1_write(IRQLATENCYCCYS);
|
||||
}
|
||||
while (waveform.toDisableBits) {
|
||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
if (!waveform.enabled && !waveform.timer1CB) {
|
||||
deinitTimer();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Speed critical bits
|
||||
#pragma GCC optimize ("O2")
|
||||
|
||||
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
|
||||
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
|
||||
static inline ICACHE_RAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
|
||||
if (ISCPUFREQ160MHZ) {
|
||||
return isCPU2X ? ccys : (ccys >> 1);
|
||||
}
|
||||
else {
|
||||
return isCPU2X ? (ccys << 1) : ccys;
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||
const bool isCPU2X = CPU2X & 1;
|
||||
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
||||
// Handle enable/disable requests from main app.
|
||||
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
waveform.toDisableBits = 0;
|
||||
}
|
||||
|
||||
if (waveform.toSetBits) {
|
||||
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
||||
Waveform& wave = waveform.pins[toSetPin];
|
||||
switch (wave.mode) {
|
||||
case WaveformMode::INIT:
|
||||
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
||||
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
||||
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy = waveform.nextEventCcy;
|
||||
}
|
||||
if (!wave.expiryCcy) {
|
||||
wave.mode = WaveformMode::INFINITE;
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case WaveformMode::UPDATEEXPIRY:
|
||||
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
||||
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
||||
wave.mode = WaveformMode::EXPIRES;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
waveform.toSetBits = 0;
|
||||
}
|
||||
|
||||
// Exit the loop if the next event, if any, is sufficiently distant.
|
||||
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
||||
uint32_t busyPins = waveform.enabled;
|
||||
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
||||
|
||||
uint32_t now = ESP.getCycleCount();
|
||||
uint32_t isrNextEventCcy = now;
|
||||
while (busyPins) {
|
||||
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
||||
waveform.nextEventCcy = isrNextEventCcy;
|
||||
break;
|
||||
}
|
||||
isrNextEventCcy = waveform.nextEventCcy;
|
||||
uint32_t loopPins = busyPins;
|
||||
while (loopPins) {
|
||||
const int pin = __builtin_ffsl(loopPins) - 1;
|
||||
const uint32_t pinBit = 1UL << pin;
|
||||
loopPins ^= pinBit;
|
||||
|
||||
Waveform& wave = waveform.pins[pin];
|
||||
|
||||
if (clockDrift) {
|
||||
wave.endDutyCcy += clockDrift;
|
||||
wave.nextPeriodCcy += clockDrift;
|
||||
wave.expiryCcy += clockDrift;
|
||||
}
|
||||
|
||||
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
||||
if (WaveformMode::EXPIRES == wave.mode &&
|
||||
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
||||
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
||||
// Disable any waveforms that are done
|
||||
waveform.enabled ^= pinBit;
|
||||
busyPins ^= pinBit;
|
||||
}
|
||||
else {
|
||||
const int32_t overshootCcys = now - waveNextEventCcy;
|
||||
if (overshootCcys >= 0) {
|
||||
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
||||
if (waveform.states & pinBit) {
|
||||
// active configuration and forward are 100% duty
|
||||
if (wave.periodCcys == wave.dutyCcys) {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
if (wave.autoPwm) {
|
||||
wave.adjDutyCcys += overshootCcys;
|
||||
}
|
||||
waveform.states ^= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 0;
|
||||
}
|
||||
else {
|
||||
GPOC = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
wave.nextPeriodCcy += periodCcys;
|
||||
if (!wave.dutyCcys) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
else {
|
||||
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
||||
if (dutyCcys <= wave.adjDutyCcys) {
|
||||
dutyCcys >>= 1;
|
||||
wave.adjDutyCcys -= dutyCcys;
|
||||
}
|
||||
else if (wave.adjDutyCcys) {
|
||||
dutyCcys -= wave.adjDutyCcys;
|
||||
wave.adjDutyCcys = 0;
|
||||
}
|
||||
wave.endDutyCcy = now + dutyCcys;
|
||||
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
||||
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||
}
|
||||
waveform.states |= pinBit;
|
||||
if (16 == pin) {
|
||||
GP16O = 1;
|
||||
}
|
||||
else {
|
||||
GPOS = pinBit;
|
||||
}
|
||||
}
|
||||
waveNextEventCcy = wave.endDutyCcy;
|
||||
}
|
||||
|
||||
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
||||
waveNextEventCcy = wave.expiryCcy;
|
||||
}
|
||||
}
|
||||
|
||||
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
||||
busyPins ^= pinBit;
|
||||
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
||||
waveform.nextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
||||
isrNextEventCcy = waveNextEventCcy;
|
||||
}
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
}
|
||||
clockDrift = 0;
|
||||
}
|
||||
|
||||
int32_t callbackCcys = 0;
|
||||
if (waveform.timer1CB) {
|
||||
callbackCcys = scaleCcys(microsecondsToClockCycles(waveform.timer1CB()), isCPU2X);
|
||||
}
|
||||
now = ESP.getCycleCount();
|
||||
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
||||
// Account for unknown duration of timer1CB().
|
||||
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
||||
waveform.nextEventCcy = now + callbackCcys;
|
||||
nextEventCcys = callbackCcys;
|
||||
}
|
||||
|
||||
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
||||
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
||||
if (isCPU2X) {
|
||||
nextEventCcys >>= 1;
|
||||
deltaIrqCcys >>= 1;
|
||||
irqLatencyCcys >>= 1;
|
||||
}
|
||||
|
||||
// Firing timer too soon, the NMI occurs before ISR has returned.
|
||||
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
||||
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
||||
nextEventCcys = irqLatencyCcys;
|
||||
}
|
||||
else {
|
||||
nextEventCcys -= deltaIrqCcys;
|
||||
}
|
||||
|
||||
// Register access is fast and edge IRQ was configured before.
|
||||
T1L = nextEventCcys;
|
||||
}
|
||||
|
||||
#endif // ESP8266
|
@ -1,93 +0,0 @@
|
||||
/*
|
||||
esp8266_waveform - General purpose waveform generation and control,
|
||||
supporting outputs on all pins in parallel.
|
||||
|
||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||
Copyright (c) 2020 Dirk O. Kaar.
|
||||
|
||||
The core idea is to have a programmable waveform generator with a unique
|
||||
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||
set to 1-shot mode and is always loaded with the time until the next edge
|
||||
of any live waveforms.
|
||||
|
||||
Up to one waveform generator per pin supported.
|
||||
|
||||
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||
timer. This allows for removing interrupt jitter and delay as the counter
|
||||
always increments once per 80MHz clock. Changes to a waveform are
|
||||
contiguous and only take effect on the next waveform transition,
|
||||
allowing for smooth transitions.
|
||||
|
||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||
|
||||
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifndef __ESP8266_WAVEFORM_H
|
||||
#define __ESP8266_WAVEFORM_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Start or change a waveform of the specified high and low times on specific pin.
|
||||
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS,
|
||||
uint32_t runTimeUS = 0, int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
||||
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
||||
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
|
||||
// full period.
|
||||
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
|
||||
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||
// Returns true or false on success or failure.
|
||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys,
|
||||
uint32_t runTimeCcys = 0, int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
||||
// Stop a waveform, if any, on the specified pin.
|
||||
// Returns true or false on success or failure.
|
||||
int stopWaveform(uint8_t pin);
|
||||
|
||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
||||
// callback returns the number of microseconds until the next desired call.
|
||||
// However, since it is called every timer1 interrupt, it may be called
|
||||
// again before this period. It should therefore use the ESP Cycle Counter
|
||||
// to determine whether or not to perform an operation.
|
||||
// Pass in NULL to disable the callback and, if no other waveforms being
|
||||
// generated, stop the timer as well.
|
||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
||||
void setTimer1Callback(uint32_t (*fn)());
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif // ESP8266
|
@ -1,269 +0,0 @@
|
||||
/*
|
||||
digital.c - wiring digital implementation for esp8266
|
||||
|
||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
#define ARDUINO_MAIN
|
||||
#include "wiring_private.h"
|
||||
#include "pins_arduino.h"
|
||||
#include "c_types.h"
|
||||
#include "eagle_soc.h"
|
||||
#include "ets_sys.h"
|
||||
#include "user_interface.h"
|
||||
#include "core_esp8266_waveform.h"
|
||||
#include "interrupts.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
volatile uint32_t* const esp8266_gpioToFn[16] PROGMEM = { &GPF0, &GPF1, &GPF2, &GPF3, &GPF4, &GPF5, &GPF6, &GPF7, &GPF8, &GPF9, &GPF10, &GPF11, &GPF12, &GPF13, &GPF14, &GPF15 };
|
||||
|
||||
extern void __pinMode(uint8_t pin, uint8_t mode) {
|
||||
if(pin < 16){
|
||||
if(mode == SPECIAL){
|
||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||
GPEC = (1 << pin); //Disable
|
||||
GPF(pin) = GPFFS(GPFFS_BUS(pin));//Set mode to BUS (RX0, TX0, TX1, SPI, HSPI or CLK depending in the pin)
|
||||
if(pin == 3) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
|
||||
} else if(mode & FUNCTION_0){
|
||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||
GPEC = (1 << pin); //Disable
|
||||
GPF(pin) = GPFFS((mode >> 4) & 0x07);
|
||||
if(pin == 13 && mode == FUNCTION_4) GPF(pin) |= (1 << GPFPU);//enable pullup on RX
|
||||
} else if(mode == OUTPUT || mode == OUTPUT_OPEN_DRAIN){
|
||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)); //SOURCE(GPIO) | DRIVER(NORMAL) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||
if(mode == OUTPUT_OPEN_DRAIN) GPC(pin) |= (1 << GPCD);
|
||||
GPES = (1 << pin); //Enable
|
||||
} else if(mode == INPUT || mode == INPUT_PULLUP){
|
||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
||||
GPEC = (1 << pin); //Disable
|
||||
GPC(pin) = (GPC(pin) & (0xF << GPCI)) | (1 << GPCD); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(UNCHANGED) | WAKEUP_ENABLE(DISABLED)
|
||||
if(mode == INPUT_PULLUP) {
|
||||
GPF(pin) |= (1 << GPFPU); // Enable Pullup
|
||||
}
|
||||
} else if(mode == WAKEUP_PULLUP || mode == WAKEUP_PULLDOWN){
|
||||
GPF(pin) = GPFFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
||||
GPEC = (1 << pin); //Disable
|
||||
if(mode == WAKEUP_PULLUP) {
|
||||
GPF(pin) |= (1 << GPFPU); // Enable Pullup
|
||||
GPC(pin) = (1 << GPCD) | (4 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(LOW) | WAKEUP_ENABLE(ENABLED)
|
||||
} else {
|
||||
GPF(pin) |= (1 << GPFPD); // Enable Pulldown
|
||||
GPC(pin) = (1 << GPCD) | (5 << GPCI) | (1 << GPCWE); //SOURCE(GPIO) | DRIVER(OPEN_DRAIN) | INT_TYPE(HIGH) | WAKEUP_ENABLE(ENABLED)
|
||||
}
|
||||
}
|
||||
} else if(pin == 16){
|
||||
GPF16 = GP16FFS(GPFFS_GPIO(pin));//Set mode to GPIO
|
||||
GPC16 = 0;
|
||||
if(mode == INPUT || mode == INPUT_PULLDOWN_16){
|
||||
if(mode == INPUT_PULLDOWN_16){
|
||||
GPF16 |= (1 << GP16FPD);//Enable Pulldown
|
||||
}
|
||||
GP16E &= ~1;
|
||||
} else if(mode == OUTPUT){
|
||||
GP16E |= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||
stopWaveform(pin);
|
||||
if(pin < 16){
|
||||
if(val) GPOS = (1 << pin);
|
||||
else GPOC = (1 << pin);
|
||||
} else if(pin == 16){
|
||||
if(val) GP16O |= 1;
|
||||
else GP16O &= ~1;
|
||||
}
|
||||
}
|
||||
|
||||
extern int ICACHE_RAM_ATTR __digitalRead(uint8_t pin) {
|
||||
if(pin < 16){
|
||||
return GPIP(pin);
|
||||
} else if(pin == 16){
|
||||
return GP16I & 0x01;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
GPIO INTERRUPTS
|
||||
*/
|
||||
|
||||
typedef void (*voidFuncPtr)(void);
|
||||
typedef void (*voidFuncPtrArg)(void*);
|
||||
|
||||
typedef struct {
|
||||
uint8_t mode;
|
||||
voidFuncPtr fn;
|
||||
void * arg;
|
||||
bool functional;
|
||||
} interrupt_handler_t;
|
||||
|
||||
//duplicate from functionalInterrupt.h keep in sync
|
||||
typedef struct InterruptInfo {
|
||||
uint8_t pin;
|
||||
uint8_t value;
|
||||
uint32_t micro;
|
||||
} InterruptInfo;
|
||||
|
||||
typedef struct {
|
||||
InterruptInfo* interruptInfo;
|
||||
void* functionInfo;
|
||||
} ArgStructure;
|
||||
|
||||
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
||||
static uint32_t interrupt_reg = 0;
|
||||
|
||||
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
|
||||
{
|
||||
(void) arg;
|
||||
(void) frame;
|
||||
uint32_t status = GPIE;
|
||||
GPIEC = status;//clear them interrupts
|
||||
uint32_t levels = GPI;
|
||||
if(status == 0 || interrupt_reg == 0) return;
|
||||
ETS_GPIO_INTR_DISABLE();
|
||||
int i = 0;
|
||||
uint32_t changedbits = status & interrupt_reg;
|
||||
while(changedbits){
|
||||
while(!(changedbits & (1 << i))) i++;
|
||||
changedbits &= ~(1 << i);
|
||||
interrupt_handler_t *handler = &interrupt_handlers[i];
|
||||
if (handler->fn &&
|
||||
(handler->mode == CHANGE ||
|
||||
(handler->mode & 1) == !!(levels & (1 << i)))) {
|
||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
||||
// we disable them before we call the client ISR
|
||||
esp8266::InterruptLock irqLock; // stop other interrupts
|
||||
if (handler->functional)
|
||||
{
|
||||
ArgStructure* localArg = (ArgStructure*)handler->arg;
|
||||
if (localArg && localArg->interruptInfo)
|
||||
{
|
||||
localArg->interruptInfo->pin = i;
|
||||
localArg->interruptInfo->value = __digitalRead(i);
|
||||
localArg->interruptInfo->micro = micros();
|
||||
}
|
||||
}
|
||||
if (handler->arg)
|
||||
{
|
||||
((voidFuncPtrArg)handler->fn)(handler->arg);
|
||||
}
|
||||
else
|
||||
{
|
||||
handler->fn();
|
||||
}
|
||||
}
|
||||
}
|
||||
ETS_GPIO_INTR_ENABLE();
|
||||
}
|
||||
|
||||
extern void cleanupFunctional(void* arg);
|
||||
|
||||
static void set_interrupt_handlers(uint8_t pin, voidFuncPtr userFunc, void* arg, uint8_t mode, bool functional)
|
||||
{
|
||||
interrupt_handler_t* handler = &interrupt_handlers[pin];
|
||||
handler->mode = mode;
|
||||
handler->fn = userFunc;
|
||||
if (handler->functional && handler->arg) // Clean when new attach without detach
|
||||
{
|
||||
cleanupFunctional(handler->arg);
|
||||
}
|
||||
handler->arg = arg;
|
||||
handler->functional = functional;
|
||||
}
|
||||
|
||||
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode, bool functional)
|
||||
{
|
||||
// #5780
|
||||
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
|
||||
if ((uint32_t)userFunc >= 0x40200000)
|
||||
{
|
||||
// ISR not in IRAM
|
||||
::printf((PGM_P)F("ISR not in IRAM!\r\n"));
|
||||
abort();
|
||||
}
|
||||
|
||||
if(pin < 16) {
|
||||
ETS_GPIO_INTR_DISABLE();
|
||||
set_interrupt_handlers(pin, (voidFuncPtr)userFunc, arg, mode, functional);
|
||||
interrupt_reg |= (1 << pin);
|
||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
||||
GPC(pin) |= ((mode & 0xF) << GPCI);//INT mode "mode"
|
||||
ETS_GPIO_INTR_ATTACH(interrupt_handler, &interrupt_reg);
|
||||
ETS_GPIO_INTR_ENABLE();
|
||||
}
|
||||
}
|
||||
|
||||
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void* arg, int mode)
|
||||
{
|
||||
__attachInterruptFunctionalArg(pin, userFunc, arg, mode, false);
|
||||
}
|
||||
|
||||
extern void ICACHE_RAM_ATTR __detachInterrupt(uint8_t pin) {
|
||||
if (pin < 16)
|
||||
{
|
||||
ETS_GPIO_INTR_DISABLE();
|
||||
GPC(pin) &= ~(0xF << GPCI);//INT mode disabled
|
||||
GPIEC = (1 << pin); //Clear Interrupt for this pin
|
||||
interrupt_reg &= ~(1 << pin);
|
||||
set_interrupt_handlers(pin, nullptr, nullptr, 0, false);
|
||||
if (interrupt_reg)
|
||||
{
|
||||
ETS_GPIO_INTR_ENABLE();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int mode)
|
||||
{
|
||||
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, 0, mode, false);
|
||||
}
|
||||
|
||||
extern void __resetPins() {
|
||||
for (int i = 0; i <= 16; ++i) {
|
||||
if (!isFlashInterfacePin(i))
|
||||
pinMode(i, INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
extern void initPins() {
|
||||
//Disable UART interrupts
|
||||
system_set_os_print(0);
|
||||
U0IE = 0;
|
||||
U1IE = 0;
|
||||
|
||||
resetPins();
|
||||
}
|
||||
|
||||
extern void resetPins() __attribute__ ((weak, alias("__resetPins")));
|
||||
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
|
||||
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
|
||||
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead"), nothrow));
|
||||
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
|
||||
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode) __attribute__((weak, alias("__attachInterruptArg")));
|
||||
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
|
||||
|
||||
};
|
||||
|
||||
#endif // ESP8266
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
pwm.c - analogWrite implementation for esp8266
|
||||
|
||||
Use the shared TIMER1 utilities to generate PWM signals
|
||||
|
||||
Original Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||
This file is part of the esp8266 core for Arduino environment.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "core_esp8266_waveform.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
static uint32_t analogMap = 0;
|
||||
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
|
||||
static uint16_t analogFreq = 1000;
|
||||
|
||||
extern void __analogWriteRange(uint32_t range) {
|
||||
if ((range >= 15) && (range <= 65535)) {
|
||||
analogScale = range;
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteResolution(int res) {
|
||||
if ((res >= 4) && (res <= 16)) {
|
||||
analogScale = (1 << res) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
extern void __analogWriteFreq(uint32_t freq) {
|
||||
if (freq < 40) {
|
||||
analogFreq = 40;
|
||||
} else if (freq > 60000) {
|
||||
analogFreq = 60000;
|
||||
} else {
|
||||
analogFreq = freq;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extern void __analogWrite(uint8_t pin, int val) {
|
||||
if (pin > 16) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t analogPeriod = microsecondsToClockCycles(1000000UL) / analogFreq;
|
||||
if (val < 0) {
|
||||
val = 0;
|
||||
} else if (val > analogScale) {
|
||||
val = analogScale;
|
||||
}
|
||||
|
||||
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
|
||||
// val: the duty cycle: between 0 (always off) and 255 (always on).
|
||||
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
|
||||
|
||||
if (analogMap & 1UL << pin) {
|
||||
analogMap &= ~(1 << pin);
|
||||
}
|
||||
else {
|
||||
pinMode(pin, OUTPUT);
|
||||
}
|
||||
uint32_t high = (analogPeriod * val) / analogScale;
|
||||
uint32_t low = analogPeriod - high;
|
||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||
int phaseReference = __builtin_ffs(analogMap) - 1;
|
||||
if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
|
||||
analogMap |= (1 << pin);
|
||||
}
|
||||
}
|
||||
|
||||
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
|
||||
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
|
||||
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
|
||||
extern void analogWriteResolution(int res) __attribute__((weak, alias("__analogWriteResolution")));
|
||||
|
||||
};
|
||||
|
||||
#endif // ESP8266
|
@ -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 "Изкл."
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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_
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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 "關閉"
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
186
tasmota/support_light_list.ino
Normal file
186
tasmota/support_light_list.ino
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
support_light_list.ino - Lightweight Linked List for simple objects - optimized for low code size and low memory
|
||||
|
||||
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* private class for Linked List element
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
template <typename T>
|
||||
class LList;
|
||||
|
||||
template <typename T>
|
||||
class LList_elt {
|
||||
public:
|
||||
|
||||
LList_elt() : _next(nullptr), _val() {}
|
||||
|
||||
inline T & val(void) { return _val; }
|
||||
inline LList_elt<T> * next(void) { return _next; }
|
||||
inline void next(LList_elt<T> * next) { _next = next; }
|
||||
|
||||
friend class LList<T>;
|
||||
|
||||
protected:
|
||||
LList_elt<T> * _next;
|
||||
T _val;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Lightweight Linked List - optimized for low code size
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
template <typename T>
|
||||
class LList {
|
||||
public:
|
||||
LList() : _head(nullptr) {}
|
||||
~LList() { reset(); }
|
||||
|
||||
// remove elements
|
||||
void removeHead(void); // remove first element
|
||||
void reset(void); // remove all elements
|
||||
void remove(const T * val);
|
||||
|
||||
// read the list
|
||||
inline bool isEmpty(void) const { return (_head == nullptr) ? true : false; }
|
||||
size_t length(void) const;
|
||||
inline T * head(void) { return _head ? &_head->_val : nullptr; }
|
||||
inline const T * head(void) const { return _head ? &_head->_val : nullptr; }
|
||||
const T * at(size_t index) const ;
|
||||
// non-const variants
|
||||
// not very academic cast but reduces code size
|
||||
inline T * at(size_t index) { return (T*) ((const LList<T>*)this)->at(index); }
|
||||
|
||||
// adding elements
|
||||
T & addHead(void);
|
||||
T & addHead(const T &val);
|
||||
T & addToLast(void);
|
||||
|
||||
// iterator
|
||||
// see https://stackoverflow.com/questions/8164567/how-to-make-my-custom-type-to-work-with-range-based-for-loops
|
||||
class iterator {
|
||||
public:
|
||||
iterator(LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||
iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||
bool operator!=(const iterator & other) const { return cur != other.cur; }
|
||||
T & operator*() const { return cur->_val; }
|
||||
private:
|
||||
LList_elt<T> *cur;
|
||||
LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||
};
|
||||
iterator begin() { return iterator(this->_head); } // start with 'head'
|
||||
iterator end() { return iterator(nullptr); } // end with null pointer
|
||||
|
||||
// const iterator
|
||||
class const_iterator {
|
||||
public:
|
||||
const_iterator(const LList_elt<T> *_cur): cur(_cur), next(nullptr) { if (cur) { next = cur->_next; } }
|
||||
const_iterator operator++() { cur = next; if (cur) { next = cur->_next;} return *this; }
|
||||
bool operator!=(const_iterator & other) const { return cur != other.cur; }
|
||||
const T & operator*() const { return cur->_val; }
|
||||
private:
|
||||
const LList_elt<T> *cur;
|
||||
const LList_elt<T> *next; // we need to keep next pointer in case the current attribute gets deleted
|
||||
};
|
||||
const_iterator begin() const { return const_iterator(this->_head); } // start with 'head'
|
||||
const_iterator end() const { return const_iterator(nullptr); } // end with null pointer
|
||||
|
||||
protected:
|
||||
LList_elt<T> * _head;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
size_t LList<T>::length(void) const {
|
||||
size_t count = 0;
|
||||
for (auto & elt : *this) {count++; }
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
const T * LList<T>::at(size_t index) const {
|
||||
size_t count = 0;
|
||||
for (const auto & elt : *this) {
|
||||
if (index == count++) { return &elt; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::reset(void) {
|
||||
while (_head) {
|
||||
LList_elt<T> * next = _head->next();
|
||||
delete _head;
|
||||
_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::removeHead(void) {
|
||||
if (_head) {
|
||||
LList_elt<T> * next = _head->next();
|
||||
delete _head;
|
||||
_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void LList<T>::remove(const T * val) {
|
||||
if (nullptr == val) { return; }
|
||||
// find element in chain and find pointer before
|
||||
LList_elt<T> **curr_ptr = &_head;
|
||||
while (*curr_ptr) {
|
||||
LList_elt<T> * curr_elt = *curr_ptr;
|
||||
if ( &(curr_elt->_val) == val) {
|
||||
*curr_ptr = curr_elt->_next; // update previous pointer to next element
|
||||
delete curr_elt;
|
||||
break; // stop iteration now
|
||||
}
|
||||
curr_ptr = &((*curr_ptr)->_next); // move to next element
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addHead(void) {
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
elt->next(_head); // insert at the head
|
||||
_head = elt;
|
||||
return elt->_val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addHead(const T &val) {
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
elt->next(_head); // insert at the head
|
||||
elt->_val = val;
|
||||
_head = elt;
|
||||
return elt->_val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T & LList<T>::addToLast(void) {
|
||||
LList_elt<T> **curr_ptr = &_head;
|
||||
while (*curr_ptr) {
|
||||
curr_ptr = &((*curr_ptr)->_next);
|
||||
}
|
||||
LList_elt<T> * elt = new LList_elt<T>(); // create element
|
||||
*curr_ptr = elt;
|
||||
return elt->_val;
|
||||
}
|
@ -237,3 +237,18 @@ public:
|
||||
_buf = nullptr;
|
||||
}
|
||||
} 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
752
tasmota/xdrv_23_zigbee_1z_libs.ino
Normal file
752
tasmota/xdrv_23_zigbee_1z_libs.ino
Normal file
@ -0,0 +1,752 @@
|
||||
/*
|
||||
xdrv_23_zigbee_1z_libs.ino - zigbee support for Tasmota, JSON replacement libs
|
||||
|
||||
Copyright (C) 2020 Theo Arends and Stephan Hadinger
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Replacement libs for JSON to output a list of attributes
|
||||
\*********************************************************************************************/
|
||||
|
||||
// simplified version of strcmp accepting both arguments to be in PMEM, and accepting nullptr arguments
|
||||
// inspired from https://code.woboq.org/userspace/glibc/string/strcmp.c.html
|
||||
int strcmp_PP(const char *p1, const char *p2) {
|
||||
if (p1 == p2) { return 0; } // equality
|
||||
if (!p1) { return -1; } // first string is null
|
||||
if (!p2) { return 1; } // second string is null
|
||||
const unsigned char *s1 = (const unsigned char *) p1;
|
||||
const unsigned char *s2 = (const unsigned char *) p2;
|
||||
unsigned char c1, c2;
|
||||
do {
|
||||
c1 = (unsigned char) pgm_read_byte(s1);
|
||||
s1++;
|
||||
c2 = (unsigned char) pgm_read_byte(s2);
|
||||
s2++;
|
||||
if (c1 == '\0')
|
||||
return c1 - c2;
|
||||
}
|
||||
while (c1 == c2);
|
||||
return c1 - c2;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Variables for Rules from last Zigbee message received
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
typedef struct Z_LastMessageVars {
|
||||
uint16_t device; // device short address
|
||||
uint16_t groupaddr; // group address
|
||||
uint16_t cluster; // cluster id
|
||||
uint8_t endpoint; // source endpoint
|
||||
} Z_LastMessageVars;
|
||||
|
||||
Z_LastMessageVars gZbLastMessage;
|
||||
|
||||
uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; }
|
||||
uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; }
|
||||
uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; }
|
||||
uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; }
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for single attribute
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
enum class Za_type : uint8_t {
|
||||
Za_none, // empty, translates into null in JSON
|
||||
// numericals
|
||||
Za_bool, // boolean, translates to true/false, uses uval32 to store
|
||||
Za_uint, // unsigned 32 int, uses uval32
|
||||
Za_int, // signed 32 int, uses ival32
|
||||
Za_float, // float 32, uses fval
|
||||
// non-nummericals
|
||||
Za_raw, // bytes buffer, uses bval
|
||||
Za_str // string, uses sval
|
||||
};
|
||||
|
||||
class Z_attribute {
|
||||
public:
|
||||
|
||||
// attribute key, either cluster+attribute_id or plain name
|
||||
union {
|
||||
struct {
|
||||
uint16_t cluster;
|
||||
uint16_t attr_id;
|
||||
} id;
|
||||
char * key;
|
||||
} key;
|
||||
// attribute value
|
||||
union {
|
||||
uint32_t uval32;
|
||||
int32_t ival32;
|
||||
float fval;
|
||||
SBuffer* bval;
|
||||
char* sval;
|
||||
} val;
|
||||
Za_type type; // uint8_t in size, type of attribute, see above
|
||||
bool key_is_str; // is the key a string?
|
||||
bool key_is_pmem; // is the string in progmem, so we don't need to make a copy
|
||||
bool val_str_raw; // if val is String, it is raw JSON and should not be escaped
|
||||
uint8_t key_suffix; // append a suffix to key (default is 1, explicitly output if >1)
|
||||
|
||||
// Constructor with all defaults
|
||||
Z_attribute():
|
||||
key{ .id = { 0x0000, 0x0000 } },
|
||||
val{ .uval32 = 0x0000 },
|
||||
type(Za_type::Za_none),
|
||||
key_is_str(false),
|
||||
key_is_pmem(false),
|
||||
val_str_raw(false),
|
||||
key_suffix(1)
|
||||
{};
|
||||
|
||||
Z_attribute(const Z_attribute & rhs) {
|
||||
deepCopy(rhs);
|
||||
}
|
||||
|
||||
Z_attribute & operator = (const Z_attribute & rhs) {
|
||||
freeKey();
|
||||
freeVal();
|
||||
deepCopy(rhs);
|
||||
}
|
||||
|
||||
// Destructor, free memory that was allocated
|
||||
~Z_attribute() {
|
||||
freeKey();
|
||||
freeVal();
|
||||
}
|
||||
|
||||
// free any allocated memoruy for values
|
||||
void freeVal(void) {
|
||||
switch (type) {
|
||||
case Za_type::Za_raw:
|
||||
if (val.bval) { delete val.bval; val.bval = nullptr; }
|
||||
break;
|
||||
case Za_type::Za_str:
|
||||
if (val.sval) { delete[] val.sval; val.sval = nullptr; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
// free any allocated memoruy for keys
|
||||
void freeKey(void) {
|
||||
if (key_is_str && key.key && !key_is_pmem) { delete[] key.key; }
|
||||
key.key = nullptr;
|
||||
}
|
||||
|
||||
// set key name
|
||||
void setKeyName(const char * _key, bool pmem = false) {
|
||||
freeKey();
|
||||
key_is_str = true;
|
||||
key_is_pmem = pmem;
|
||||
if (pmem) {
|
||||
key.key = (char*) _key;
|
||||
} else {
|
||||
setKeyName(_key, nullptr);
|
||||
}
|
||||
}
|
||||
// provide two entries and concat
|
||||
void setKeyName(const char * _key, const char * _key2) {
|
||||
freeKey();
|
||||
key_is_str = true;
|
||||
key_is_pmem = false;
|
||||
if (_key) {
|
||||
size_t key_len = strlen_P(_key);
|
||||
if (_key2) {
|
||||
key_len += strlen_P(_key2);
|
||||
}
|
||||
key.key = new char[key_len+1];
|
||||
strcpy_P(key.key, _key);
|
||||
if (_key2) {
|
||||
strcat_P(key.key, _key2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setKeyId(uint16_t cluster, uint16_t attr_id) {
|
||||
freeKey();
|
||||
key_is_str = false;
|
||||
key.id.cluster = cluster;
|
||||
key.id.attr_id = attr_id;
|
||||
}
|
||||
|
||||
// Setters
|
||||
void setNone(void) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = 0;
|
||||
type = Za_type::Za_none;
|
||||
}
|
||||
void setUInt(uint32_t _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = _val;
|
||||
type = Za_type::Za_uint;
|
||||
}
|
||||
void setBool(bool _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.uval32 = _val;
|
||||
type = Za_type::Za_bool;
|
||||
}
|
||||
void setInt(int32_t _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.ival32 = _val;
|
||||
type = Za_type::Za_int;
|
||||
}
|
||||
void setFloat(float _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val.fval = _val;
|
||||
type = Za_type::Za_float;
|
||||
}
|
||||
|
||||
void setBuf(const SBuffer &buf, size_t index, size_t len) {
|
||||
freeVal();
|
||||
if (len) {
|
||||
val.bval = new SBuffer(len);
|
||||
val.bval->addBuffer(buf.buf(index), len);
|
||||
}
|
||||
type = Za_type::Za_raw;
|
||||
}
|
||||
|
||||
// set the string value
|
||||
// PMEM argument is allowed
|
||||
// string will be copied, so it can be changed later
|
||||
// nullptr is allowed and considered as empty string
|
||||
// Note: memory is allocated only if string is non-empty
|
||||
void setStr(const char * _val) {
|
||||
freeVal(); // free any previously allocated memory
|
||||
val_str_raw = false;
|
||||
// val.sval is always nullptr after freeVal()
|
||||
if (_val) {
|
||||
size_t len = strlen_P(_val);
|
||||
if (len) {
|
||||
val.sval = new char[len+1];
|
||||
strcpy_P(val.sval, _val);
|
||||
}
|
||||
}
|
||||
type = Za_type::Za_str;
|
||||
}
|
||||
inline void setStrRaw(const char * _val) {
|
||||
setStr(_val);
|
||||
val_str_raw = true;
|
||||
}
|
||||
|
||||
inline bool isNum(void) const { return (type >= Za_type::Za_bool) && (type <= Za_type::Za_float); }
|
||||
// get num values
|
||||
float getFloat(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return (float) val.uval32;
|
||||
case Za_type::Za_int: return (float) val.ival32;
|
||||
case Za_type::Za_float: return val.fval;
|
||||
default: return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t getInt(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return (int32_t) val.uval32;
|
||||
case Za_type::Za_int: return val.ival32;
|
||||
case Za_type::Za_float: return (int32_t) val.fval;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getUInt(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return val.uval32;
|
||||
case Za_type::Za_int: return (uint32_t) val.ival32;
|
||||
case Za_type::Za_float: return (uint32_t) val.fval;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool getBool(void) const {
|
||||
switch (type) {
|
||||
case Za_type::Za_bool:
|
||||
case Za_type::Za_uint: return val.uval32 ? true : false;
|
||||
case Za_type::Za_int: return val.ival32 ? true : false;
|
||||
case Za_type::Za_float: return val.fval ? true : false;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
const SBuffer * getRaw(void) const {
|
||||
if (Za_type::Za_raw == type) { return val.bval; }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// always return a point to a string, if not defined then empty string.
|
||||
// Never returns nullptr
|
||||
const char * getStr(void) const {
|
||||
if (Za_type::Za_str == type) { return val.sval; }
|
||||
return "";
|
||||
}
|
||||
|
||||
bool equalsKey(const Z_attribute & attr2, bool ignore_key_suffix = false) const {
|
||||
// check if keys are equal
|
||||
if (key_is_str != attr2.key_is_str) { return false; }
|
||||
if (key_is_str) {
|
||||
if (strcmp_PP(key.key, attr2.key.key)) { return false; }
|
||||
} else {
|
||||
if ((key.id.cluster != attr2.key.id.cluster) ||
|
||||
(key.id.attr_id != attr2.key.id.attr_id)) { return false; }
|
||||
}
|
||||
if (!ignore_key_suffix) {
|
||||
if (key_suffix != attr2.key_suffix) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool equalsKey(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const {
|
||||
if (!key_is_str) {
|
||||
if ((key.id.cluster == cluster) && (key.id.attr_id == attr_id)) {
|
||||
if (suffix) {
|
||||
if (key_suffix == suffix) { return true; }
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool equalsKey(const char * name, uint8_t suffix = 0) const {
|
||||
if (key_is_str) {
|
||||
if (0 == strcmp_PP(key.key, name)) {
|
||||
if (suffix) {
|
||||
if (key_suffix == suffix) { return true; }
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool equalsVal(const Z_attribute & attr2) const {
|
||||
if (type != attr2.type) { return false; }
|
||||
if ((type >= Za_type::Za_bool) && (type <= Za_type::Za_float)) {
|
||||
// numerical value
|
||||
if (val.uval32 != attr2.val.uval32) { return false; }
|
||||
} else if (type == Za_type::Za_raw) {
|
||||
// compare 2 Static buffers
|
||||
return equalsSBuffer(val.bval, attr2.val.bval);
|
||||
} else if (type == Za_type::Za_str) {
|
||||
// if (val_str_raw != attr2.val_str_raw) { return false; }
|
||||
if (strcmp_PP(val.sval, attr2.val.sval)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool equals(const Z_attribute & attr2) const {
|
||||
return equalsKey(attr2) && equalsVal(attr2);
|
||||
}
|
||||
|
||||
String toString(bool prefix_comma = false) const {
|
||||
String res("");
|
||||
if (prefix_comma) { res += ','; }
|
||||
res += '"';
|
||||
// compute the attribute name
|
||||
if (key_is_str) {
|
||||
if (key.key) { res += EscapeJSONString(key.key); }
|
||||
else { res += F("null"); } // shouldn't happen
|
||||
if (key_suffix > 1) {
|
||||
res += key_suffix;
|
||||
}
|
||||
} else {
|
||||
char attr_name[12];
|
||||
snprintf_P(attr_name, sizeof(attr_name), PSTR("%04X/%04X"), key.id.cluster, key.id.attr_id);
|
||||
res += attr_name;
|
||||
if (key_suffix > 1) {
|
||||
res += '+';
|
||||
res += key_suffix;
|
||||
}
|
||||
}
|
||||
res += F("\":");
|
||||
// value part
|
||||
switch (type) {
|
||||
case Za_type::Za_none:
|
||||
res += "null";
|
||||
break;
|
||||
case Za_type::Za_bool:
|
||||
res += val.uval32 ? F("true") : F("false");
|
||||
break;
|
||||
case Za_type::Za_uint:
|
||||
res += val.uval32;
|
||||
break;
|
||||
case Za_type::Za_int:
|
||||
res += val.ival32;
|
||||
break;
|
||||
case Za_type::Za_float:
|
||||
{
|
||||
String fstr(val.fval, 2);
|
||||
size_t last = fstr.length() - 1;
|
||||
// remove trailing zeros
|
||||
while (fstr[last] == '0') {
|
||||
fstr.remove(last--);
|
||||
}
|
||||
// remove trailing dot
|
||||
if (fstr[last] == '.') {
|
||||
fstr.remove(last);
|
||||
}
|
||||
res += fstr;
|
||||
}
|
||||
break;
|
||||
case Za_type::Za_raw:
|
||||
res += '"';
|
||||
if (val.bval) {
|
||||
size_t blen = val.bval->len();
|
||||
// print as HEX
|
||||
char hex[2*blen+1];
|
||||
ToHex_P(val.bval->getBuffer(), blen, hex, sizeof(hex));
|
||||
res += hex;
|
||||
}
|
||||
res += '"';
|
||||
break;
|
||||
case Za_type::Za_str:
|
||||
if (val_str_raw) {
|
||||
if (val.sval) { res += val.sval; }
|
||||
} else {
|
||||
res += '"';
|
||||
if (val.sval) {
|
||||
res += EscapeJSONString(val.sval); // escape JSON chars
|
||||
}
|
||||
res += '"';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// copy value from one attribute to another, without changing its type
|
||||
void copyVal(const Z_attribute & rhs) {
|
||||
freeVal();
|
||||
// copy value
|
||||
val.uval32 = 0x00000000;
|
||||
type = rhs.type;
|
||||
if (rhs.isNum()) {
|
||||
val.uval32 = rhs.val.uval32;
|
||||
} else if (rhs.type == Za_type::Za_raw) {
|
||||
if (rhs.val.bval) {
|
||||
val.bval = new SBuffer(rhs.val.bval->len());
|
||||
val.bval->addBuffer(*(rhs.val.bval));
|
||||
}
|
||||
} else if (rhs.type == Za_type::Za_str) {
|
||||
if (rhs.val.sval) {
|
||||
size_t s_len = strlen_P(rhs.val.sval);
|
||||
val.sval = new char[s_len+1];
|
||||
strcpy_P(val.sval, rhs.val.sval);
|
||||
}
|
||||
}
|
||||
val_str_raw = rhs.val_str_raw;
|
||||
}
|
||||
|
||||
protected:
|
||||
void deepCopy(const Z_attribute & rhs) {
|
||||
// copy key
|
||||
if (!rhs.key_is_str) {
|
||||
key.id.cluster = rhs.key.id.cluster;
|
||||
key.id.attr_id = rhs.key.id.attr_id;
|
||||
} else {
|
||||
if (rhs.key_is_pmem) {
|
||||
key.key = rhs.key.key; // PMEM, don't copy
|
||||
} else {
|
||||
key.key = nullptr;
|
||||
if (rhs.key.key) {
|
||||
size_t key_len = strlen_P(rhs.key.key);
|
||||
if (key_len) {
|
||||
key.key = new char[key_len+1];
|
||||
strcpy_P(key.key, rhs.key.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
key_is_str = rhs.key_is_str;
|
||||
key_is_pmem = rhs.key_is_pmem;
|
||||
key_suffix = rhs.key_suffix;
|
||||
// copy value
|
||||
copyVal(rhs);
|
||||
// don't touch next pointer
|
||||
}
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for attribute array of values
|
||||
* This is a helper function to generate a clean list of unsigned ints
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
class Z_json_array {
|
||||
public:
|
||||
|
||||
Z_json_array(): val("[]") {} // start with empty array
|
||||
void add(uint32_t uval32) {
|
||||
// remove trailing ']'
|
||||
val.remove(val.length()-1);
|
||||
if (val.length() > 1) { // if not empty, prefix with comma
|
||||
val += ',';
|
||||
}
|
||||
val += uval32;
|
||||
val += ']';
|
||||
}
|
||||
String &toString(void) {
|
||||
return val;
|
||||
}
|
||||
|
||||
private :
|
||||
String val;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
*
|
||||
* Class for attribute ordered list
|
||||
*
|
||||
\*********************************************************************************************/
|
||||
|
||||
|
||||
// Attribute list
|
||||
// Contains meta-information:
|
||||
// - source endpoint (is conflicting)
|
||||
// - LQI (if not conflicting)
|
||||
class Z_attribute_list : public LList<Z_attribute> {
|
||||
public:
|
||||
uint8_t src_ep; // source endpoint, 0xFF if unknown
|
||||
uint8_t lqi; // linkquality, 0xFF if unknown
|
||||
uint16_t group_id; // group address OxFFFF if inknown
|
||||
|
||||
Z_attribute_list():
|
||||
LList<Z_attribute>(), // call superclass constructor
|
||||
src_ep(0xFF),
|
||||
lqi(0xFF),
|
||||
group_id(0xFFFF)
|
||||
{};
|
||||
|
||||
// we don't define any destructor, the superclass destructor is automatically called
|
||||
|
||||
// reset object to its initial state
|
||||
// free all allocated memory
|
||||
void reset(void) {
|
||||
LList<Z_attribute>::reset();
|
||||
src_ep = 0xFF;
|
||||
lqi = 0xFF;
|
||||
group_id = 0xFFFF;
|
||||
}
|
||||
|
||||
inline bool isValidSrcEp(void) const { return 0xFF != src_ep; }
|
||||
inline bool isValidLQI(void) const { return 0xFF != lqi; }
|
||||
inline bool isValidGroupId(void) const { return 0xFFFF != group_id; }
|
||||
|
||||
// the following addAttribute() compute the suffix and increments it
|
||||
// Add attribute to the list, given cluster and attribute id
|
||||
Z_attribute & addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||
|
||||
// Add attribute to the list, given name
|
||||
Z_attribute & addAttribute(const char * name, bool pmem = false, uint8_t suffix = 0);
|
||||
Z_attribute & addAttribute(const char * name, const char * name2, uint8_t suffix = 0);
|
||||
inline Z_attribute & addAttribute(const __FlashStringHelper * name, uint8_t suffix = 0) {
|
||||
return addAttribute((const char*) name, true, suffix);
|
||||
}
|
||||
|
||||
// Remove from list by reference, if null or not found, then do nothing
|
||||
inline void removeAttribute(const Z_attribute * attr) { remove(attr); }
|
||||
|
||||
// dump the entire structure as JSON, starting from head (as parameter)
|
||||
// does not start not end with a comma
|
||||
// do we enclosed in brackets '{' '}'
|
||||
String toString(bool enclose_brackets = false) const;
|
||||
|
||||
// find if attribute with same key already exists, return null if not found
|
||||
const Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) const;
|
||||
const Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) const;
|
||||
const Z_attribute * findAttribute(const Z_attribute &attr) const; // suffis always count here
|
||||
// non-const variants
|
||||
inline Z_attribute * findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0) {
|
||||
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(cluster, attr_id, suffix);
|
||||
}
|
||||
inline Z_attribute * findAttribute(const char * name, uint8_t suffix = 0) {
|
||||
return (Z_attribute*) (((const Z_attribute_list*)this)->findAttribute(name, suffix));
|
||||
}
|
||||
inline Z_attribute * findAttribute(const Z_attribute &attr) {
|
||||
return (Z_attribute*) ((const Z_attribute_list*)this)->findAttribute(attr);
|
||||
}
|
||||
|
||||
// count matching attributes, ignoring suffix
|
||||
size_t countAttribute(uint16_t cluster, uint16_t attr_id) const ;
|
||||
size_t countAttribute(const char * name) const ;
|
||||
|
||||
// if suffix == 0, we don't care and find the first match
|
||||
Z_attribute & findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix = 0);
|
||||
Z_attribute & findOrCreateAttribute(const char * name, uint8_t suffix = 0);
|
||||
// always care about suffix
|
||||
Z_attribute & findOrCreateAttribute(const Z_attribute &attr);
|
||||
// replace attribute with new value, suffix does care
|
||||
Z_attribute & replaceOrCreate(const Z_attribute &attr);
|
||||
|
||||
// merge with secondary list, return true if ok, false if conflict
|
||||
bool mergeList(const Z_attribute_list &list2);
|
||||
};
|
||||
|
||||
// add a cluster/attr_id attribute at the end of the list
|
||||
Z_attribute & Z_attribute_list::addAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.key.id.cluster = cluster;
|
||||
attr.key.id.attr_id = attr_id;
|
||||
attr.key_is_str = false;
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.id.cluster, attr.key.id.attr_id); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
// add a cluster/attr_id attribute at the end of the list
|
||||
Z_attribute & Z_attribute_list::addAttribute(const char * name, bool pmem, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.setKeyName(name, pmem);
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
Z_attribute & Z_attribute_list::addAttribute(const char * name, const char * name2, uint8_t suffix) {
|
||||
Z_attribute & attr = addToLast();
|
||||
attr.setKeyName(name, name2);
|
||||
if (!suffix) { attr.key_suffix = countAttribute(attr.key.key); }
|
||||
else { attr.key_suffix = suffix; }
|
||||
return attr;
|
||||
}
|
||||
|
||||
String Z_attribute_list::toString(bool enclose_brackets) const {
|
||||
String res = "";
|
||||
if (enclose_brackets) { res += '{'; }
|
||||
bool prefix_comma = false;
|
||||
for (const auto & attr : *this) {
|
||||
res += attr.toString(prefix_comma);
|
||||
prefix_comma = true;
|
||||
}
|
||||
// add source endpoint
|
||||
if (0xFF != src_ep) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_ENDPOINT "\":");
|
||||
res += src_ep;
|
||||
}
|
||||
// add group address
|
||||
if (0xFFFF != group_id) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_GROUP "\":");
|
||||
res += group_id;
|
||||
}
|
||||
// add lqi
|
||||
if (0xFF != lqi) {
|
||||
if (prefix_comma) { res += ','; }
|
||||
prefix_comma = true;
|
||||
res += F("\"" D_CMND_ZIGBEE_LINKQUALITY "\":");
|
||||
res += lqi;
|
||||
}
|
||||
if (enclose_brackets) { res += '}'; }
|
||||
// done
|
||||
return res;
|
||||
}
|
||||
|
||||
// suffis always count here
|
||||
const Z_attribute * Z_attribute_list::findAttribute(const Z_attribute &attr) const {
|
||||
uint8_t suffix = attr.key_suffix;
|
||||
if (attr.key_is_str) {
|
||||
return findAttribute(attr.key.key, suffix);
|
||||
} else {
|
||||
return findAttribute(attr.key.id.cluster, attr.key.id.attr_id, suffix);
|
||||
}
|
||||
}
|
||||
|
||||
const Z_attribute * Z_attribute_list::findAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) const {
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(cluster, attr_id, suffix)) { return &attr; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
size_t Z_attribute_list::countAttribute(uint16_t cluster, uint16_t attr_id) const {
|
||||
size_t count = 0;
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(cluster, attr_id, 0)) { count++; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// return the existing attribute or create a new one
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(uint16_t cluster, uint16_t attr_id, uint8_t suffix) {
|
||||
Z_attribute * found = findAttribute(cluster, attr_id, suffix);
|
||||
return found ? *found : addAttribute(cluster, attr_id, suffix);
|
||||
}
|
||||
|
||||
const Z_attribute * Z_attribute_list::findAttribute(const char * name, uint8_t suffix) const {
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(name, suffix)) { return &attr; }
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
size_t Z_attribute_list::countAttribute(const char * name) const {
|
||||
size_t count = 0;
|
||||
for (const auto & attr : *this) {
|
||||
if (attr.equalsKey(name, 0)) { count++; }
|
||||
}
|
||||
return count;
|
||||
}
|
||||
// return the existing attribute or create a new one
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(const char * name, uint8_t suffix) {
|
||||
Z_attribute * found = findAttribute(name, suffix);
|
||||
return found ? *found : addAttribute(name, suffix);
|
||||
}
|
||||
|
||||
// same but passing a Z_attribute as key
|
||||
Z_attribute & Z_attribute_list::findOrCreateAttribute(const Z_attribute &attr) {
|
||||
if (attr.key_is_str) {
|
||||
return findOrCreateAttribute(attr.key.key, attr.key_suffix);
|
||||
} else {
|
||||
return findOrCreateAttribute(attr.key.id.cluster, attr.key.id.attr_id, attr.key_suffix);
|
||||
}
|
||||
}
|
||||
// replace the entire content with new attribute or create
|
||||
Z_attribute & Z_attribute_list::replaceOrCreate(const Z_attribute &attr) {
|
||||
Z_attribute &new_attr = findOrCreateAttribute(attr);
|
||||
new_attr.copyVal(attr);
|
||||
return new_attr;
|
||||
}
|
||||
|
||||
|
||||
bool Z_attribute_list::mergeList(const Z_attribute_list &attr_list) {
|
||||
// Check source endpoint
|
||||
if (0xFF == src_ep) {
|
||||
src_ep = attr_list.src_ep;
|
||||
} else if (0xFF != attr_list.src_ep) {
|
||||
if (src_ep != attr_list.src_ep) { return false; }
|
||||
}
|
||||
if (0xFF != attr_list.lqi) {
|
||||
lqi = attr_list.lqi;
|
||||
}
|
||||
for (auto & attr : attr_list) {
|
||||
replaceOrCreate(attr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // USE_ZIGBEE
|
@ -19,8 +19,6 @@
|
||||
|
||||
#ifdef USE_ZIGBEE
|
||||
|
||||
#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
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user