From 27288ac60363989a3809a9d0316844052bef1804 Mon Sep 17 00:00:00 2001
From: Ajith Vasudevan
Date: Fri, 19 Mar 2021 15:56:48 +0530
Subject: [PATCH 01/35] Added support for MAX7219 Seven-Segment Display
---
lib/lib_display/LedControl/LICENSE | 23 +
lib/lib_display/LedControl/README.md | 25 +
lib/lib_display/LedControl/keywords.txt | 28 +
lib/lib_display/LedControl/library.properties | 10 +
lib/lib_display/LedControl/src/LedControl.cpp | 211 +++++
lib/lib_display/LedControl/src/LedControl.h | 190 ++++
tasmota/language/af_AF.h | 3 +
tasmota/language/bg_BG.h | 3 +
tasmota/language/cs_CZ.h | 3 +
tasmota/language/de_DE.h | 3 +
tasmota/language/el_GR.h | 3 +
tasmota/language/en_GB.h | 3 +
tasmota/language/es_ES.h | 3 +
tasmota/language/fr_FR.h | 3 +
tasmota/language/fy_NL.h | 3 +
tasmota/language/he_HE.h | 3 +
tasmota/language/hu_HU.h | 3 +
tasmota/language/it_IT.h | 3 +
tasmota/language/ko_KO.h | 3 +
tasmota/language/nl_NL.h | 3 +
tasmota/language/pl_PL.h | 3 +
tasmota/language/pt_BR.h | 3 +
tasmota/language/pt_PT.h | 3 +
tasmota/language/ro_RO.h | 3 +
tasmota/language/ru_RU.h | 3 +
tasmota/language/sk_SK.h | 3 +
tasmota/language/sv_SE.h | 3 +
tasmota/language/tr_TR.h | 3 +
tasmota/language/uk_UA.h | 3 +
tasmota/language/vi_VN.h | 3 +
tasmota/language/zh_CN.h | 3 +
tasmota/language/zh_TW.h | 3 +
tasmota/tasmota_configurations.h | 1 +
tasmota/tasmota_template.h | 9 +-
tasmota/xdrv_13_display.ino | 16 +-
tasmota/xdsp_16_max7219.ino | 878 ++++++++++++++++++
36 files changed, 1457 insertions(+), 12 deletions(-)
create mode 100644 lib/lib_display/LedControl/LICENSE
create mode 100644 lib/lib_display/LedControl/README.md
create mode 100644 lib/lib_display/LedControl/keywords.txt
create mode 100644 lib/lib_display/LedControl/library.properties
create mode 100644 lib/lib_display/LedControl/src/LedControl.cpp
create mode 100644 lib/lib_display/LedControl/src/LedControl.h
create mode 100644 tasmota/xdsp_16_max7219.ino
diff --git a/lib/lib_display/LedControl/LICENSE b/lib/lib_display/LedControl/LICENSE
new file mode 100644
index 000000000..8d59812aa
--- /dev/null
+++ b/lib/lib_display/LedControl/LICENSE
@@ -0,0 +1,23 @@
+LedControl.h - A library for controling Leds with a MAX7219/MAX7221
+Copyright (c) 2007-2015 Eberhard Fahle
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+This permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/lib_display/LedControl/README.md b/lib/lib_display/LedControl/README.md
new file mode 100644
index 000000000..8c832d654
--- /dev/null
+++ b/lib/lib_display/LedControl/README.md
@@ -0,0 +1,25 @@
+LedControl
+==========
+LedControl is an [Arduino](http://arduino.cc) library for MAX7219 and MAX7221 Led display drivers.
+The code also works with the [Teensy (3.1)](https://www.pjrc.com/teensy/)
+
+Documentation
+-------------
+Documentation for the library is on the [Github Project Pages](http://wayoda.github.io/LedControl/)
+
+Download
+--------
+The lastest binary version of the Library is always available from the
+[LedControl Release Page](https://github.com/wayoda/LedControl/releases)
+
+
+Install
+-------
+The library can be installed using the [standard Arduino library install procedure](http://arduino.cc/en/Guide/Libraries#.UwxndHX5PtY)
+
+
+
+
+
+
+
diff --git a/lib/lib_display/LedControl/keywords.txt b/lib/lib_display/LedControl/keywords.txt
new file mode 100644
index 000000000..9d2a94f1f
--- /dev/null
+++ b/lib/lib_display/LedControl/keywords.txt
@@ -0,0 +1,28 @@
+#######################################
+# Syntax Coloring Map For LedControl
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+LedControl KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+shutdown KEYWORD2
+setScanLimit KEYWORD2
+setIntensity KEYWORD2
+clearDisplay KEYWORD2
+setLed KEYWORD2
+setRow KEYWORD2
+setColumn KEYWORD2
+setDigit KEYWORD2
+setChar KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################
+
diff --git a/lib/lib_display/LedControl/library.properties b/lib/lib_display/LedControl/library.properties
new file mode 100644
index 000000000..66baced18
--- /dev/null
+++ b/lib/lib_display/LedControl/library.properties
@@ -0,0 +1,10 @@
+name=LedControl
+version=1.0.6
+author=Eberhard Fahle
+maintainer=Eberhard Fahle
+sentence=A library for the MAX7219 and the MAX7221 Led display drivers.
+paragraph=The library supports multiple daisychained drivers and supports Led-Matrix displays as well as 7-Segment displays.
+category=Display
+url=http://wayoda.github.io/LedControl/
+architectures=*
+
diff --git a/lib/lib_display/LedControl/src/LedControl.cpp b/lib/lib_display/LedControl/src/LedControl.cpp
new file mode 100644
index 000000000..e43211fd8
--- /dev/null
+++ b/lib/lib_display/LedControl/src/LedControl.cpp
@@ -0,0 +1,211 @@
+/*
+ * LedControl.cpp - A library for controling Leds with a MAX7219/MAX7221
+ * Copyright (c) 2007 Eberhard Fahle
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * This permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include "LedControl.h"
+
+//the opcodes for the MAX7221 and MAX7219
+#define OP_NOOP 0
+#define OP_DIGIT0 1
+#define OP_DIGIT1 2
+#define OP_DIGIT2 3
+#define OP_DIGIT3 4
+#define OP_DIGIT4 5
+#define OP_DIGIT5 6
+#define OP_DIGIT6 7
+#define OP_DIGIT7 8
+#define OP_DECODEMODE 9
+#define OP_INTENSITY 10
+#define OP_SCANLIMIT 11
+#define OP_SHUTDOWN 12
+#define OP_DISPLAYTEST 15
+
+LedControl::LedControl(int dataPin, int clkPin, int csPin, int numDevices) {
+ SPI_MOSI=dataPin;
+ SPI_CLK=clkPin;
+ SPI_CS=csPin;
+ if(numDevices<=0 || numDevices>8 )
+ numDevices=8;
+ maxDevices=numDevices;
+ pinMode(SPI_MOSI,OUTPUT);
+ pinMode(SPI_CLK,OUTPUT);
+ pinMode(SPI_CS,OUTPUT);
+ digitalWrite(SPI_CS,HIGH);
+ SPI_MOSI=dataPin;
+ for(int i=0;i<64;i++)
+ status[i]=0x00;
+ for(int i=0;i=maxDevices)
+ return;
+ if(b)
+ spiTransfer(addr, OP_SHUTDOWN,0);
+ else
+ spiTransfer(addr, OP_SHUTDOWN,1);
+}
+
+void LedControl::setScanLimit(int addr, int limit) {
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(limit>=0 && limit<8)
+ spiTransfer(addr, OP_SCANLIMIT,limit);
+}
+
+void LedControl::setIntensity(int addr, int intensity) {
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(intensity>=0 && intensity<16)
+ spiTransfer(addr, OP_INTENSITY,intensity);
+}
+
+void LedControl::clearDisplay(int addr) {
+ int offset;
+
+ if(addr<0 || addr>=maxDevices)
+ return;
+ offset=addr*8;
+ for(int i=0;i<8;i++) {
+ status[offset+i]=0;
+ spiTransfer(addr, i+1,status[offset+i]);
+ }
+}
+
+void LedControl::setLed(int addr, int row, int column, boolean state) {
+ int offset;
+ byte val=0x00;
+
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(row<0 || row>7 || column<0 || column>7)
+ return;
+ offset=addr*8;
+ val=B10000000 >> column;
+ if(state)
+ status[offset+row]=status[offset+row]|val;
+ else {
+ val=~val;
+ status[offset+row]=status[offset+row]&val;
+ }
+ spiTransfer(addr, row+1,status[offset+row]);
+}
+
+void LedControl::setRow(int addr, int row, byte value) {
+ int offset;
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(row<0 || row>7)
+ return;
+ offset=addr*8;
+ status[offset+row]=value;
+ spiTransfer(addr, row+1,status[offset+row]);
+}
+
+void LedControl::setColumn(int addr, int col, byte value) {
+ byte val;
+
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(col<0 || col>7)
+ return;
+ for(int row=0;row<8;row++) {
+ val=value >> (7-row);
+ val=val & 0x01;
+ setLed(addr,row,col,val);
+ }
+}
+
+void LedControl::setDigit(int addr, int digit, byte value, boolean dp) {
+ int offset;
+ byte v;
+
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(digit<0 || digit>7 || value>15)
+ return;
+ offset=addr*8;
+ v=pgm_read_byte_near(charTable + value);
+ if(dp)
+ v|=B10000000;
+ status[offset+digit]=v;
+ spiTransfer(addr, digit+1,v);
+}
+
+void LedControl::setChar(int addr, int digit, char value, boolean dp) {
+ int offset;
+ byte index,v;
+
+ if(addr<0 || addr>=maxDevices)
+ return;
+ if(digit<0 || digit>7)
+ return;
+ offset=addr*8;
+ index=(byte)value;
+ if(index >127) {
+ //no defined beyond index 127, so we use the space char
+ index=32;
+ }
+ v=pgm_read_byte_near(charTable + index);
+ if(dp)
+ v|=B10000000;
+ status[offset+digit]=v;
+ spiTransfer(addr, digit+1,v);
+}
+
+void LedControl::spiTransfer(int addr, volatile byte opcode, volatile byte data) {
+ //Create an array with the data to shift out
+ int offset=addr*2;
+ int maxbytes=maxDevices*2;
+
+ for(int i=0;i0;i--)
+ shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]);
+ //latch the data onto the display
+ digitalWrite(SPI_CS,HIGH);
+}
+
+
diff --git a/lib/lib_display/LedControl/src/LedControl.h b/lib/lib_display/LedControl/src/LedControl.h
new file mode 100644
index 000000000..cdfaa1f5a
--- /dev/null
+++ b/lib/lib_display/LedControl/src/LedControl.h
@@ -0,0 +1,190 @@
+/*
+ * LedControl.h - A library for controling Leds with a MAX7219/MAX7221
+ * Copyright (c) 2007 Eberhard Fahle
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * This permission notice shall be included in all copies or
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef LedControl_h
+#define LedControl_h
+
+#include
+
+#if (ARDUINO >= 100)
+#include
+#else
+#include
+#endif
+
+/*
+ * Segments to be switched on for characters and digits on
+ * 7-Segment Displays
+ */
+const static byte charTable [] PROGMEM = {
+ B01111110,B00110000,B01101101,B01111001,B00110011,B01011011,B01011111,B01110000,
+ B01111111,B01111011,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B00000000,B00000000,B00000000,B10000000,B00000001,B10000000,B00000000,
+ B01111110,B00110000,B01101101,B01111001,B00110011,B01011011,B01011111,B01110000,
+ B01111111,B01111011,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,B00000000,
+ B00110111,B00000000,B00000000,B00000000,B00001110,B00000000,B00000000,B00000000,
+ B01100111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00001000,
+ B00000000,B01110111,B00011111,B00001101,B00111101,B01001111,B01000111,B00000000,
+ B00110111,B00000000,B00000000,B00000000,B00001110,B00000000,B00010101,B00011101,
+ B01100111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
+ B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000
+};
+
+class LedControl {
+ private :
+ /* The array for shifting the data to the devices */
+ byte spidata[16];
+ /* Send out a single command to the device */
+ void spiTransfer(int addr, byte opcode, byte data);
+
+ /* We keep track of the led-status for all 8 devices in this array */
+ byte status[64];
+ /* Data is shifted out of this pin*/
+ int SPI_MOSI;
+ /* The clock is signaled on this pin */
+ int SPI_CLK;
+ /* This one is driven LOW for chip selectzion */
+ int SPI_CS;
+ /* The maximum number of devices we use */
+ int maxDevices;
+
+ public:
+ /*
+ * Create a new controler
+ * Params :
+ * dataPin pin on the Arduino where data gets shifted out
+ * clockPin pin for the clock
+ * csPin pin for selecting the device
+ * numDevices maximum number of devices that can be controled
+ */
+ LedControl(int dataPin, int clkPin, int csPin, int numDevices=1);
+
+ /*
+ * Gets the number of devices attached to this LedControl.
+ * Returns :
+ * int the number of devices on this LedControl
+ */
+ int getDeviceCount();
+
+ /*
+ * Set the shutdown (power saving) mode for the device
+ * Params :
+ * addr The address of the display to control
+ * status If true the device goes into power-down mode. Set to false
+ * for normal operation.
+ */
+ void shutdown(int addr, bool status);
+
+ /*
+ * Set the number of digits (or rows) to be displayed.
+ * See datasheet for sideeffects of the scanlimit on the brightness
+ * of the display.
+ * Params :
+ * addr address of the display to control
+ * limit number of digits to be displayed (1..8)
+ */
+ void setScanLimit(int addr, int limit);
+
+ /*
+ * Set the brightness of the display.
+ * Params:
+ * addr the address of the display to control
+ * intensity the brightness of the display. (0..15)
+ */
+ void setIntensity(int addr, int intensity);
+
+ /*
+ * Switch all Leds on the display off.
+ * Params:
+ * addr address of the display to control
+ */
+ void clearDisplay(int addr);
+
+ /*
+ * Set the status of a single Led.
+ * Params :
+ * addr address of the display
+ * row the row of the Led (0..7)
+ * col the column of the Led (0..7)
+ * state If true the led is switched on,
+ * if false it is switched off
+ */
+ void setLed(int addr, int row, int col, boolean state);
+
+ /*
+ * Set all 8 Led's in a row to a new state
+ * Params:
+ * addr address of the display
+ * row row which is to be set (0..7)
+ * value each bit set to 1 will light up the
+ * corresponding Led.
+ */
+ void setRow(int addr, int row, byte value);
+
+ /*
+ * Set all 8 Led's in a column to a new state
+ * Params:
+ * addr address of the display
+ * col column which is to be set (0..7)
+ * value each bit set to 1 will light up the
+ * corresponding Led.
+ */
+ void setColumn(int addr, int col, byte value);
+
+ /*
+ * Display a hexadecimal digit on a 7-Segment Display
+ * Params:
+ * addr address of the display
+ * digit the position of the digit on the display (0..7)
+ * value the value to be displayed. (0x00..0x0F)
+ * dp sets the decimal point.
+ */
+ void setDigit(int addr, int digit, byte value, boolean dp);
+
+ /*
+ * Display a character on a 7-Segment display.
+ * There are only a few characters that make sense here :
+ * '0','1','2','3','4','5','6','7','8','9','0',
+ * 'A','b','c','d','E','F','H','L','P',
+ * '.','-','_',' '
+ * Params:
+ * addr address of the display
+ * digit the position of the character on the display (0..7)
+ * value the character to be displayed.
+ * dp sets the decimal point.
+ */
+ void setChar(int addr, int digit, char value, boolean dp);
+};
+
+#endif //LedControl.h
+
+
+
diff --git a/tasmota/language/af_AF.h b/tasmota/language/af_AF.h
index b61c82c6e..7693ec42c 100644
--- a/tasmota/language/af_AF.h
+++ b/tasmota/language/af_AF.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h
index 2061eb460..96d1783c1 100644
--- a/tasmota/language/bg_BG.h
+++ b/tasmota/language/bg_BG.h
@@ -642,6 +642,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h
index be329c3e2..9359c0c3a 100644
--- a/tasmota/language/cs_CZ.h
+++ b/tasmota/language/cs_CZ.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h
index 9eaff2441..e042317bd 100644
--- a/tasmota/language/de_DE.h
+++ b/tasmota/language/de_DE.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h
index ca14da7c1..677ea5c1d 100644
--- a/tasmota/language/el_GR.h
+++ b/tasmota/language/el_GR.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h
index 12a476a32..a2afce3f0 100644
--- a/tasmota/language/en_GB.h
+++ b/tasmota/language/en_GB.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h
index 93967a818..4316797d7 100644
--- a/tasmota/language/es_ES.h
+++ b/tasmota/language/es_ES.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h
index 980b380f7..c31f97859 100644
--- a/tasmota/language/fr_FR.h
+++ b/tasmota/language/fr_FR.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/fy_NL.h b/tasmota/language/fy_NL.h
index 8b166dd94..ca6688f57 100644
--- a/tasmota/language/fy_NL.h
+++ b/tasmota/language/fy_NL.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h
index 8bf5e5e97..0b4374f5c 100644
--- a/tasmota/language/he_HE.h
+++ b/tasmota/language/he_HE.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h
index 239239348..5c4b3a150 100644
--- a/tasmota/language/hu_HU.h
+++ b/tasmota/language/hu_HU.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h
index b8a7d828b..c449f9362 100644
--- a/tasmota/language/it_IT.h
+++ b/tasmota/language/it_IT.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 - CLK"
#define D_SENSOR_TM1638_DIO "TM1638 - DIO"
#define D_SENSOR_TM1638_STB "TM1638 - STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 - DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 - CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 - CLK"
#define D_SENSOR_HX711_SCK "HX711 - SCK"
#define D_SENSOR_HX711_DAT "HX711 - DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h
index 964ab46a7..442aaa4cb 100644
--- a/tasmota/language/ko_KO.h
+++ b/tasmota/language/ko_KO.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h
index bc855e4a8..87ad91eae 100644
--- a/tasmota/language/nl_NL.h
+++ b/tasmota/language/nl_NL.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h
index 78710996f..da1151789 100644
--- a/tasmota/language/pl_PL.h
+++ b/tasmota/language/pl_PL.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h
index a2929b529..138799cbd 100644
--- a/tasmota/language/pt_BR.h
+++ b/tasmota/language/pt_BR.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h
index c5b524875..c448703bd 100644
--- a/tasmota/language/pt_PT.h
+++ b/tasmota/language/pt_PT.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h
index c8182eb75..9329d95ef 100644
--- a/tasmota/language/ro_RO.h
+++ b/tasmota/language/ro_RO.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h
index 6d8d0b932..ac7a9479a 100644
--- a/tasmota/language/ru_RU.h
+++ b/tasmota/language/ru_RU.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h
index b0fbc5c3f..1342d88ae 100644
--- a/tasmota/language/sk_SK.h
+++ b/tasmota/language/sk_SK.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h
index 5ccbc77fe..1300e6295 100644
--- a/tasmota/language/sv_SE.h
+++ b/tasmota/language/sv_SE.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h
index e6429fed1..bcf01e1ea 100644
--- a/tasmota/language/tr_TR.h
+++ b/tasmota/language/tr_TR.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h
index 286b851dc..27527d226 100644
--- a/tasmota/language/uk_UA.h
+++ b/tasmota/language/uk_UA.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h
index c04e43010..5b9b2c9c2 100644
--- a/tasmota/language/vi_VN.h
+++ b/tasmota/language/vi_VN.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h
index a7e1847ca..9f99d4cf8 100644
--- a/tasmota/language/zh_CN.h
+++ b/tasmota/language/zh_CN.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h
index 13cc6e858..cfeed1ff7 100644
--- a/tasmota/language/zh_TW.h
+++ b/tasmota/language/zh_TW.h
@@ -643,6 +643,9 @@
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
#define D_SENSOR_TM1638_DIO "TM1638 DIO"
#define D_SENSOR_TM1638_STB "TM1638 STB"
+#define D_SENSOR_MAX7219_DIN "MAX7219 DIN"
+#define D_SENSOR_MAX7219_CS "MAX7219 CS"
+#define D_SENSOR_MAX7219_CLK "MAX7219 CLK"
#define D_SENSOR_HX711_SCK "HX711 SCK"
#define D_SENSOR_HX711_DAT "HX711 DAT"
#define D_SENSOR_FTC532 "FTC532"
diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h
index c4956bdbd..fa25e67f0 100644
--- a/tasmota/tasmota_configurations.h
+++ b/tasmota/tasmota_configurations.h
@@ -306,6 +306,7 @@
#define USE_DISPLAY // Add Display Support (+2k code)
#define USE_DISPLAY_TM1637 // [DisplayModel 15] Enable TM1637 module
+ #define USE_DISPLAY_MAX7219 // [DisplayModel 16] Enable MAX7219 7-segment module
#define USE_I2C // I2C using library wire (+10k code, 0k2 mem, 124 iram)
#define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0
diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h
index 197596b7b..4bdeee62d 100644
--- a/tasmota/tasmota_template.h
+++ b/tasmota/tasmota_template.h
@@ -155,6 +155,7 @@ enum UserSelectablePins {
GPIO_XPT2046_CS, // XPT2046 SPI Chip Select
GPIO_CSE7761_TX, GPIO_CSE7761_RX, // CSE7761 Serial interface (Dual R3)
GPIO_VL53L0X_XSHUT1, // VL53L0X_XSHUT (the max number of sensors is VL53L0X_MAX_SENSORS)- Used when connecting multiple VL53L0X
+ GPIO_MAX7219CLK, GPIO_MAX7219DIN, GPIO_MAX7219CS, // MAX7219 interface
GPIO_SENSOR_END };
enum ProgramSelectablePins {
@@ -330,6 +331,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_XPT2046_CS "|"
D_SENSOR_CSE7761_TX "|" D_SENSOR_CSE7761_RX "|"
D_SENSOR_VL53L0X_XSHUT "|"
+ D_SENSOR_MAX7219_CLK "|" D_SENSOR_MAX7219_DIN "|" D_SENSOR_MAX7219_CS "|"
;
const char kSensorNamesFixed[] PROGMEM =
@@ -789,8 +791,13 @@ const uint16_t kGpioNiceList[] PROGMEM = {
#endif
#ifdef USE_VL53L0X
AGPIO(GPIO_VL53L0X_XSHUT1) + VL53L0X_MAX_SENSORS, // When using multiple VL53L0X.
-#endif
+#endif
+#ifdef USE_DISPLAY_MAX7219
+ AGPIO(GPIO_MAX7219CLK),
+ AGPIO(GPIO_MAX7219DIN),
+ AGPIO(GPIO_MAX7219CS),
+#endif // USE_DISPLAY_MAX7219
/*-------------------------------------------------------------------------------------------*\
* ESP32 specifics
\*-------------------------------------------------------------------------------------------*/
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index 35e4e05ee..fd2c3964d 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -42,7 +42,7 @@ uint16_t bg_color = 0;
uint8_t color_type = COLOR_BW;
uint8_t auto_draw = 1;
-const uint8_t DISPLAY_MAX_DRIVERS = 16; // Max number of display drivers/models supported by xdsp_interface.ino
+const uint8_t DISPLAY_MAX_DRIVERS = 17; // Max number of display drivers/models supported by xdsp_interface.ino
const uint8_t DISPLAY_MAX_COLS = 64; // Max number of columns allowed with command DisplayCols
const uint8_t DISPLAY_MAX_ROWS = 64; // Max number of lines allowed with command DisplayRows
@@ -1873,15 +1873,9 @@ void CmndDisplayScrollText(void)
void CmndDisplaySize(void)
{
-#ifdef USE_DISPLAY_TM1637
- if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 6)) {
-#else
- if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) {
-#endif
- Settings.display_size = XdrvMailbox.payload;
- if (renderer) renderer->setTextSize(Settings.display_size);
- //else DisplaySetSize(Settings.display_size);
- }
+ Settings.display_size = XdrvMailbox.payload;
+ if (renderer) renderer->setTextSize(Settings.display_size);
+ //else DisplaySetSize(Settings.display_size);
ResponseCmndNumber(Settings.display_size);
}
@@ -1945,7 +1939,7 @@ void CmndDisplayText(void)
#ifndef USE_DISPLAY_MODES1TO5
DisplayText();
#else
- if(Settings.display_model == 15) {
+ if(Settings.display_model == 15 || Settings.display_model == 16) {
XdspCall(FUNC_DISPLAY_SEVENSEG_TEXT);
} else if (!Settings.display_mode) {
DisplayText();
diff --git a/tasmota/xdsp_16_max7219.ino b/tasmota/xdsp_16_max7219.ino
new file mode 100644
index 000000000..c8a39b280
--- /dev/null
+++ b/tasmota/xdsp_16_max7219.ino
@@ -0,0 +1,878 @@
+/*
+ xdsp_16_max7219.ino - Support for MAX7219- based seven-segment displays for Tasmota
+
+ Copyright (C) 2021 Ajith Vasudevan
+
+ 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 .
+*/
+
+#ifdef USE_DISPLAY
+#ifdef USE_DISPLAY_MAX7219
+/*********************************************************************************************\
+ This driver enables the display of numbers (both integers and floats) and basic text
+ on the inexpensive MAX7219-based seven-segment modules.
+
+ Raw segments can also be displayed.
+
+ In addition, it is also possible to set brightness (8 levels), clear the display, scroll text,
+ display a rudimentary bar graph, and a Clock (12 hr and 24 hr).
+
+ To use, compile Tasmota with USE_DISPLAY and USE_DISPLAY_MAX7219, or build the tasmota-display env.
+
+ Connect the MAX7219 display module's pins to any free GPIOs of the ESP8266 module
+ and assign the pins as follows from Tasmota's GUI:
+
+ DIN hardware pin --> "MAX7219 DIN"
+ CS hardware pin --> "MAX7219 CS"
+ CLK hardware pin --> "MAX7219 CLK"
+
+
+ Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, set the Display Model to 16
+ using the command "DisplayModel 16"
+
+ After the ESP8266/ESP32 module restarts again, the following "Display" commands can be used:
+
+
+ DisplayClear
+
+ Clears the display, command: "DisplayClear"
+
+
+ DisplayNumber num [,position {0-(MAX7219Data.num_digits-1))} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
+
+ Clears and then displays number without decimal. command e.g., "DisplayNumber 1234"
+ Control 'leading zeros', 'length' and 'position' with "DisplayNumber 1234, , , "
+ 'leading zeros' can be 1 or 0 (default), 'length' can be 1 to MAX7219Data.num_digits, 'position' can be 0 (left-most) to MAX7219Data.num_digits (right-most).
+ See function description below for more details.
+
+ DisplayNumberNC num [,position {0-(MAX7219Data.num_digits-1))} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
+
+ Display integer number as above, but without clearing first. e.g., "DisplayNumberNC 1234". Usage is same as above.
+
+
+
+ DisplayFloat num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
+
+ Clears and then displays float (with decimal point) command e.g., "DisplayFloat 12.34"
+ See function description below for more details.
+
+
+
+ DisplayFloatNC num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
+
+ Displays float (with decimal point) as above, but without clearing first. command e.g., "DisplayFloatNC 12.34"
+ See function description below for more details.
+
+
+
+ DisplayBrightness num {1-8}
+
+ Set brightness (1 to 8) command e.g., "DisplayBrightness 2"
+
+
+
+ DisplayRaw position {0-(MAX7219Data.num_digits-1)},length {1 to MAX7219Data.num_digits}, num1 [, num2[, num3[, num4[, ...upto MAX7219Data.num_digits numbers]]]]]
+
+ Takes upto MAX7219Data.num_digits comma-separated integers (0-255) and displays raw segments. Each number represents a
+ 7-segment digit. Each 8-bit number represents individual segments of a digit.
+ For example, the command "DisplayRaw 0, 4, 255, 255, 255, 255" would display "[8.8.8.8.]"
+
+
+
+ DisplayText text [, position {0-(MAX7219Data.num_digits-1)} [,length {1 to MAX7219Data.num_digits}]]
+
+ Clears and then displays basic text. command e.g., "DisplayText ajith vasudevan"
+ Control 'length' and 'position' with "DisplayText , , "
+ 'length' can be 1 to MAX7219Data.num_digits, 'position' can be 0 (left-most) to MAX7219Data.num_digits-1 (right-most)
+ A caret(^) symbol in the text input is dispayed as the degrees(°) symbol. This is useful for displaying Temperature!
+ For example, the command "DisplayText 22.5^" will display "22.5°".
+
+
+ DisplayTextNC text [, position {0-MAX7219Data.num_digits-1} [,length {1 to MAX7219Data.num_digits}]]
+
+ Clears first, then displays text. Usage is same as above.
+
+
+
+ DisplayScrollText text
+
+ Displays scrolling text.
+
+
+
+ DisplayScrollDelay delay {0-15} // default = 4
+
+ Sets the speed of text scroll. Smaller delay = faster scrolling.
+
+
+
+ DisplayLevel num {0-100}
+
+ Display a horizontal bar graph (0-100) command e.g., "DisplayLevel 50" will display [|||| ]
+
+
+
+ DisplayClock 1|2|0
+
+ Displays a clock.
+ Commands "DisplayClock 1" // 12 hr format
+ "DisplayClock 2" // 24 hr format
+ "DisplayClock 0" // turn off clock
+
+
+
+\*********************************************************************************************/
+
+#define XDSP_16 16
+
+#define BRIGHTNESS_MIN 1
+#define BRIGHTNESS_MAX 8
+#define CMD_MAX_LEN 55
+#define LEVEL_MIN 0
+#define LEVEL_MAX 100
+#define SCROLL_MAX_LEN 50
+#define POSITION_MIN 0
+#define POSITION_MAX 8
+#define LED_MIN 0
+#define LED_MAX 255
+#define MAX7219_ADDR 0
+
+#include
+
+LedControl *max7219display;
+
+struct
+{
+ char scroll_text[CMD_MAX_LEN];
+ char msg[60];
+ char model_name[8];
+ uint8_t num_digits = 4;
+ uint8_t scroll_delay = 4;
+ uint8_t scroll_index = 0;
+ uint8_t iteration = 0;
+ uint8_t brightness = 5;
+ uint8_t prev_buttons;
+
+ bool init_done = false;
+ bool scroll = false;
+ bool show_clock = false;
+ bool clock_24 = false;
+} MAX7219Data;
+
+/*********************************************************************************************\
+* Init function
+\*********************************************************************************************/
+void MAXDriverInit(void)
+{
+
+ if (!(PinUsed(GPIO_MAX7219DIN) && PinUsed(GPIO_MAX7219CLK) && PinUsed(GPIO_MAX7219CS)))
+ return;
+
+ Settings.display_model == XDSP_16;
+ MAX7219Data.num_digits = 8;
+
+ strcpy(MAX7219Data.model_name, "MAX7219");
+ max7219display = new LedControl(Pin(GPIO_MAX7219DIN), Pin(GPIO_MAX7219CLK), Pin(GPIO_MAX7219CS), 1);
+ max7219display->shutdown(MAX7219_ADDR, false);
+
+ maxClearDisplay();
+ MAX7219Data.brightness = (Settings.display_dimmer ? Settings.display_dimmer : MAX7219Data.brightness);
+ maxSetBrightness(MAX7219Data.brightness);
+ MAX7219Data.init_done = true;
+ AddLog(LOG_LEVEL_INFO, PSTR("DSP: %s display driver initialized with %d digits"), MAX7219Data.model_name, MAX7219Data.num_digits);
+}
+
+// Function to display specified ascii char at specified position for MAX7219
+void displayMAX7219ASCII(uint8_t pos, char c)
+{
+ pos = 7 - pos;
+ max7219display->setChar(MAX7219_ADDR, pos, c, false);
+}
+
+// Function to display specified ascii char with dot at specified position for MAX7219
+void displayMAX7219ASCIIwDot(uint8_t pos, char c)
+{
+ pos = 7 - pos;
+ max7219display->setChar(MAX7219_ADDR, pos, c, true);
+}
+
+// Function to display raw segments at specified position for MAX7219
+void displayMAX72197Seg(uint8_t pos, uint8_t seg)
+{
+ bool dec_bit = seg & 128;
+ seg = seg << 1;
+ seg = seg | dec_bit;
+ uint8_t NO_OF_BITS = 8;
+ uint8_t reverse_num = 0;
+ for (uint8_t i = 0; i < NO_OF_BITS; i++)
+ {
+ if ((seg & (1 << i)))
+ reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
+ }
+ seg = reverse_num;
+
+ pos = 7 - pos;
+ max7219display->setRow(MAX7219_ADDR, pos, seg);
+}
+
+/*********************************************************************************************\
+* Displays number without decimal, with/without leading zeros, specifying start-position
+* and length, optionally skipping clearing display before displaying the number.
+* commands: DisplayNumber num [,position {0-(MAX7219Data.num_digits-1)} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
+* DisplayNumberNC num [,position {0-(MAX7219Data.num_digits-1)} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]] // "NC" --> "No Clear"
+\*********************************************************************************************/
+bool MAXCmndNumber(bool clear)
+{
+ char sNum[CMD_MAX_LEN];
+ char sLeadingzeros[CMD_MAX_LEN];
+ char sPosition[CMD_MAX_LEN];
+ char sLength[CMD_MAX_LEN];
+ uint8_t length = 0;
+ bool leadingzeros = false;
+ uint8_t position = 0;
+
+ uint32_t num = 0;
+
+ switch (ArgC())
+ {
+ case 4:
+ subStr(sLength, XdrvMailbox.data, ",", 4);
+ length = atoi(sLength);
+ case 3:
+ subStr(sLeadingzeros, XdrvMailbox.data, ",", 3);
+ leadingzeros = atoi(sLeadingzeros);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sNum, XdrvMailbox.data, ",", 1);
+ num = atof(sNum);
+ }
+
+ if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
+ position = 0;
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: num %d, pos %d, lead %d, len %d"), num, position, leadingzeros, length);
+
+ if (clear)
+ maxClearDisplay();
+
+ char txt[30];
+ snprintf_P(txt, sizeof(txt), PSTR("%d"), num);
+ if (!length)
+ length = strlen(txt);
+ if ((length < 0) || (length > MAX7219Data.num_digits))
+ length = MAX7219Data.num_digits;
+
+ char pad = (leadingzeros ? '0' : ' ');
+ uint32_t i = position;
+ uint8_t rawBytes[1];
+
+ for (; i < position + (length - strlen(txt)); i++)
+ {
+ if (i > MAX7219Data.num_digits)
+ break;
+ displayMAX7219ASCII(i, pad);
+ }
+
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > MAX7219Data.num_digits)
+ break;
+ if (txt[j] == 0)
+ break;
+ displayMAX7219ASCII(i, txt[j]);
+ }
+
+ return true;
+}
+
+/*********************************************************************************************\
+* Displays number with decimal, specifying position, precision and length,
+* optionally skipping clearing display before displaying the number.
+* commands: DisplayFloat num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
+* DisplayFloatNC num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]] // "NC" --> "No Clear"
+\*********************************************************************************************/
+bool MAXCmndFloat(bool clear)
+{
+
+ char sNum[CMD_MAX_LEN];
+ char sPrecision[CMD_MAX_LEN];
+ char sPosition[CMD_MAX_LEN];
+ char sLength[CMD_MAX_LEN];
+ uint8_t length = 0;
+ uint8_t precision = MAX7219Data.num_digits;
+ uint8_t position = 0;
+
+ float fnum = 0.0f;
+
+ switch (ArgC())
+ {
+ case 4:
+ subStr(sLength, XdrvMailbox.data, ",", 4);
+ length = atoi(sLength);
+ case 3:
+ subStr(sPrecision, XdrvMailbox.data, ",", 3);
+ precision = atoi(sPrecision);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sNum, XdrvMailbox.data, ",", 1);
+ fnum = atof(sNum);
+ }
+
+ if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
+ position = 0;
+ if ((precision < 0) || (precision > MAX7219Data.num_digits))
+ precision = MAX7219Data.num_digits;
+
+ if (clear)
+ maxClearDisplay();
+
+ char txt[30];
+ ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), precision, &fnum);
+
+ if (!length)
+ length = strlen(txt);
+ if ((length <= 0) || (length > MAX7219Data.num_digits))
+ length = MAX7219Data.num_digits;
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: num %4_f, prec %d, len %d"), &fnum, precision, length);
+
+ for (uint32_t i = 0, j = 0; i < length; i++, j++)
+ {
+ if ((j + position) > 7)
+ break;
+ if (txt[i] == 0)
+ break;
+ if (txt[i + 1] == '.')
+ {
+ displayMAX7219ASCIIwDot(j + position, txt[i]);
+ i++;
+ length++;
+ }
+ else
+ displayMAX7219ASCII(j + position, txt[i]);
+ }
+ return true;
+}
+
+// /*********************************************************************************************\
+// * Clears the display
+// * Command: DisplayClear
+// \*********************************************************************************************/
+bool MAXCmndClear(void)
+{
+ maxClearDisplay();
+ sprintf(MAX7219Data.msg, PSTR("Cleared"));
+ XdrvMailbox.data = MAX7219Data.msg;
+ return true;
+}
+
+// /*********************************************************************************************\
+// * Clears the display
+// \*********************************************************************************************/
+void maxClearDisplay(void)
+{
+ max7219display->clearDisplay(MAX7219_ADDR);
+}
+
+/*********************************************************************************************\
+* Display scrolling text
+* Command: DisplayMAX7219Data.scroll_text text
+\*********************************************************************************************/
+bool MAXCmndScrollText(void)
+{
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: Text %s"), XdrvMailbox.data);
+
+ if (XdrvMailbox.data_len > SCROLL_MAX_LEN)
+ {
+ snprintf(MAX7219Data.msg, sizeof(MAX7219Data.msg), PSTR("Text too long. Length should be less than %d"), SCROLL_MAX_LEN);
+ XdrvMailbox.data = MAX7219Data.msg;
+ return false;
+ }
+ else
+ {
+ snprintf(MAX7219Data.scroll_text, sizeof(MAX7219Data.scroll_text), PSTR(" "));
+ snprintf(MAX7219Data.scroll_text, sizeof(MAX7219Data.scroll_text), PSTR("%s"), XdrvMailbox.data);
+ MAX7219Data.scroll_text[XdrvMailbox.data_len] = 0;
+ MAX7219Data.scroll_index = 0;
+ MAX7219Data.scroll = true;
+ return true;
+ }
+}
+
+/*********************************************************************************************\
+* Sets the scroll delay for scrolling text.
+* Command: DisplayMAX7219Data.scroll_delay delay {0-15} // default = 4
+\*********************************************************************************************/
+bool MAXCmndScrollDelay(void)
+{
+ if (ArgC() == 0)
+ {
+ XdrvMailbox.payload = MAX7219Data.scroll_delay;
+ return true;
+ }
+ if (MAX7219Data.scroll_delay < 0)
+ MAX7219Data.scroll_delay = 0;
+ MAX7219Data.scroll_delay = XdrvMailbox.payload;
+ return true;
+}
+
+/*********************************************************************************************\
+* Scrolls a given string. Called every 50ms
+\*********************************************************************************************/
+void maxScrollText(void)
+{
+ MAX7219Data.iteration++;
+ if (MAX7219Data.scroll_delay)
+ MAX7219Data.iteration = MAX7219Data.iteration % MAX7219Data.scroll_delay;
+ else
+ MAX7219Data.iteration = 0;
+ if (MAX7219Data.iteration)
+ return;
+
+ if (MAX7219Data.scroll_index > strlen(MAX7219Data.scroll_text))
+ {
+ MAX7219Data.scroll = false;
+ MAX7219Data.scroll_index = 0;
+ return;
+ }
+ uint8_t rawBytes[1];
+ for (uint32_t i = 0, j = MAX7219Data.scroll_index; i < 1 + strlen(MAX7219Data.scroll_text); i++, j++)
+ {
+ if (i > (MAX7219Data.num_digits - 1))
+ {
+ break;
+ }
+ rawBytes[0] = tm1637display->encode(MAX7219Data.scroll_text[j]);
+ bool dotSkipped = false;
+ if (MAX7219Data.scroll_text[j + 1] == '.')
+ {
+ dotSkipped = true;
+ rawBytes[0] = rawBytes[0] | 128;
+ j++;
+ }
+ else if (MAX7219Data.scroll_text[j] == '^')
+ {
+ rawBytes[0] = 1 | 2 | 32 | 64;
+ }
+ if (!dotSkipped && MAX7219Data.scroll_text[j] == '.')
+ {
+ j++;
+ MAX7219Data.scroll_index++;
+ rawBytes[0] = tm1637display->encode(MAX7219Data.scroll_text[j]);
+ }
+ if (MAX7219Data.scroll_text[j + 1] == '.')
+ {
+ rawBytes[0] = rawBytes[0] | 128;
+ }
+ displayMAX72197Seg(i, rawBytes[0]);
+ }
+ MAX7219Data.scroll_index++;
+}
+
+/*********************************************************************************************\
+* Displays a horizontal bar graph. Takes a percentage number (0-100) as input
+* Command: DisplayLevel level {0-100}
+\*********************************************************************************************/
+bool MAXCmndLevel(void)
+{
+ uint16_t val = XdrvMailbox.payload;
+ if ((val < LEVEL_MIN) || (val > LEVEL_MAX))
+ {
+ Response_P(PSTR("{\"Error\":\"Level should be a number in the range [%d, %d]\"}"), LEVEL_MIN, LEVEL_MAX);
+ return false;
+ }
+
+ uint8_t totalBars = 2 * MAX7219Data.num_digits;
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.model_name %s MAXCmndLevel totalBars=%d"), MAX7219Data.model_name, totalBars);
+ float barsToDisplay = totalBars * val / 100.0f;
+ char txt[5];
+ ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), 1, &barsToDisplay);
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.model_name %s MAXCmndLevel barsToDisplay=%s"), MAX7219Data.model_name, txt);
+ char s[4];
+ ext_snprintf_P(s, sizeof(s), PSTR("%0_f"), &barsToDisplay);
+ uint8_t numBars = atoi(s);
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: CmndTM1637Level numBars %d"), numBars);
+
+ maxClearDisplay();
+ uint8_t rawBytes[1];
+ for (int i = 1; i <= numBars; i++)
+ {
+ uint8_t digit = (i - 1) / 2;
+ uint8_t value = (((i % 2) == 0) ? 54 : 48);
+ displayMAX72197Seg(digit, value);
+ }
+ return true;
+}
+
+/*********************************************************************************************\
+* Display arbitrary data on the display module
+* Command: DisplayRaw position {0-(MAX7219Data.num_digits-1)},length {1 to MAX7219Data.num_digits}, a [, b[, c[, d[...upto MAX7219Data.num_digits]]]]
+* where a,b,c,d... are upto MAX7219Data.num_digits numbers in the range 0-255, each number (byte)
+* corresponding to a single 7-segment digit. Within each byte, bit 0 is segment A,
+* bit 1 is segment B etc. The function may either set the entire display
+* or any desired part using the length and position parameters.
+\*********************************************************************************************/
+bool MAXCmndRaw(void)
+{
+ uint8_t DATA[8] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+ char as[CMD_MAX_LEN];
+ char bs[CMD_MAX_LEN];
+ char cs[CMD_MAX_LEN];
+ char ds[CMD_MAX_LEN];
+ char es[CMD_MAX_LEN];
+ char fs[CMD_MAX_LEN];
+ char gs[CMD_MAX_LEN];
+ char hs[CMD_MAX_LEN];
+
+ char sLength[CMD_MAX_LEN];
+ char sPos[CMD_MAX_LEN];
+
+ uint32_t position = 0;
+ uint32_t length = 0;
+
+ switch (ArgC())
+ {
+ case 10:
+ subStr(hs, XdrvMailbox.data, ",", 10);
+ DATA[7] = atoi(hs);
+ case 9:
+ subStr(gs, XdrvMailbox.data, ",", 9);
+ DATA[6] = atoi(gs);
+ case 8:
+ subStr(fs, XdrvMailbox.data, ",", 8);
+ DATA[5] = atoi(fs);
+ case 7:
+ subStr(es, XdrvMailbox.data, ",", 7);
+ DATA[4] = atoi(es);
+ case 6:
+ subStr(ds, XdrvMailbox.data, ",", 6);
+ DATA[3] = atoi(ds);
+ case 5:
+ subStr(cs, XdrvMailbox.data, ",", 5);
+ DATA[2] = atoi(cs);
+ case 4:
+ subStr(bs, XdrvMailbox.data, ",", 4);
+ DATA[1] = atoi(bs);
+ case 3:
+ subStr(as, XdrvMailbox.data, ",", 3);
+ DATA[0] = atoi(as);
+ case 2:
+ subStr(sLength, XdrvMailbox.data, ",", 2);
+ length = atoi(sLength);
+ case 1:
+ subStr(sPos, XdrvMailbox.data, ",", 1);
+ position = atoi(sPos);
+ }
+
+ if (!length)
+ length = ArgC() - 2;
+ if (length < 0 || length > MAX7219Data.num_digits)
+ length = MAX7219Data.num_digits;
+ if (position < 0 || position > (MAX7219Data.num_digits - 1))
+ position = 0;
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: a %d, b %d, c %d, d %d, e %d, f %d, g %d, h %d, len %d, pos %d"),
+ DATA[0], DATA[1], DATA[2], DATA[3], DATA[4], DATA[5], DATA[6], DATA[7], length, position);
+
+ for (uint32_t i = position; i < position + length; i++)
+ {
+ if (i > 7)
+ break;
+ displayMAX72197Seg(i, DATA[i - position]);
+ }
+
+ return true;
+}
+
+/*********************************************************************************************\
+* Display a given string.
+* Text can be placed at arbitrary location on the display using the length and
+* position parameters without affecting the rest of the display.
+* Command: DisplayText text [, position {0-(MAX7219Data.num_digits-1)} [,length {1 to MAX7219Data.num_digits}]]
+\*********************************************************************************************/
+bool MAXCmndText(bool clear)
+{
+ char sString[CMD_MAX_LEN + 1];
+ char sPosition[CMD_MAX_LEN];
+ char sLength[CMD_MAX_LEN];
+ uint8_t length = 0;
+ uint8_t position = 0;
+
+ switch (ArgC())
+ {
+ case 3:
+ subStr(sLength, XdrvMailbox.data, ",", 3);
+ length = atoi(sLength);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sString, XdrvMailbox.data, ",", 1);
+ }
+
+ if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
+ position = 0;
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: sString %s, pos %d, len %d"), sString, position, length);
+
+ if (clear)
+ maxClearDisplay();
+
+ if (!length)
+ length = strlen(sString);
+ if ((length < 0) || (length > MAX7219Data.num_digits))
+ length = MAX7219Data.num_digits;
+
+ uint32_t i = position;
+ uint8_t rawBytes[1];
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > (MAX7219Data.num_digits - 1))
+ break;
+ if (sString[j] == 0)
+ break;
+ rawBytes[0] = tm1637display->encode(sString[j]);
+ bool dotSkipped = false;
+ if (sString[j + 1] == '.')
+ {
+ dotSkipped = true;
+ rawBytes[0] = rawBytes[0] | 128;
+ j++;
+ }
+ else if (sString[j] == '^')
+ {
+ rawBytes[0] = 1 | 2 | 32 | 64;
+ }
+ if (!dotSkipped && sString[j] == '.')
+ rawBytes[0] = 128;
+ displayMAX72197Seg(i, rawBytes[0]);
+ }
+
+ return true;
+}
+
+/*********************************************************************************************\
+* Sets brightness of the display.
+* Command: DisplayBrightness {1-8}
+\*********************************************************************************************/
+bool MAXCmndBrightness(void)
+{
+
+ uint16_t val = XdrvMailbox.payload;
+ if (ArgC() == 0)
+ {
+ XdrvMailbox.payload = MAX7219Data.brightness;
+ return true;
+ }
+
+ if ((val < BRIGHTNESS_MIN) || (val > BRIGHTNESS_MAX))
+ {
+ Response_P(PSTR("{\"Error\":\"Brightness should be a number in the range [%d, %d]\"}"), BRIGHTNESS_MIN, BRIGHTNESS_MAX);
+ return false;
+ }
+ MAX7219Data.brightness = val;
+ maxSetBrightness(MAX7219Data.brightness);
+ return true;
+}
+
+void maxSetBrightness(uint8_t val)
+{
+ if ((val < BRIGHTNESS_MIN) || (val > BRIGHTNESS_MAX))
+ val = 5;
+ Settings.display_dimmer = val;
+ max7219display->setIntensity(MAX7219_ADDR, val - 1);
+}
+
+/*********************************************************************************************\
+* Displays a clock.
+* Command: DisplayClock 1 // 12-hour format
+* DisplayClock 2 // 24-hour format
+* DisplayClock 0 // turn off clock and clear
+\*********************************************************************************************/
+bool MAXCmndClock(void)
+{
+
+ MAX7219Data.show_clock = XdrvMailbox.payload;
+
+ if (ArgC() == 0)
+ XdrvMailbox.payload = 1;
+ if (XdrvMailbox.payload > 1)
+ MAX7219Data.clock_24 = true;
+ else if (XdrvMailbox.payload == 1)
+ MAX7219Data.clock_24 = false;
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.show_clock %d, MAX7219Data.clock_24 %d"), MAX7219Data.show_clock, MAX7219Data.clock_24);
+
+ maxClearDisplay();
+ return true;
+}
+
+/*********************************************************************************************\
+* refreshes the time if clock is displayed
+\*********************************************************************************************/
+void maxShowTime()
+{
+ uint8_t hr = RtcTime.hour;
+ uint8_t mn = RtcTime.minute;
+ // uint8_t hr = 1;
+ // uint8_t mn = 0;
+ char z = ' ';
+ if (MAX7219Data.clock_24)
+ {
+ z = '0';
+ }
+ else
+ {
+ if (hr > 12)
+ hr -= 12;
+ if (hr == 0)
+ hr = 12;
+ }
+
+ char tm[5];
+ if (hr < 10)
+ {
+ if (mn < 10)
+ snprintf(tm, sizeof(tm), PSTR("%c%d0%d"), z, hr, mn);
+ else
+ snprintf(tm, sizeof(tm), PSTR("%c%d%d"), z, hr, mn);
+ }
+ else
+ {
+ if (mn < 10)
+ snprintf(tm, sizeof(tm), PSTR("%d0%d"), hr, mn);
+ else
+ snprintf(tm, sizeof(tm), PSTR("%d%d"), hr, mn);
+ }
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((millis() % 1000) > 500 && (i == 1))
+ displayMAX7219ASCIIwDot(i, tm[i]);
+ else
+ displayMAX7219ASCII(i, tm[i]);
+ }
+}
+
+/*********************************************************************************************\
+* This function is called for all Display functions.
+\*********************************************************************************************/
+bool MAXMainFunc(uint8_t fn)
+{
+ bool result = false;
+
+ if (XdrvMailbox.data_len > CMD_MAX_LEN)
+ {
+ Response_P(PSTR("{\"Error\":\"Command text too long. Please limit it to %d characters\"}"), CMD_MAX_LEN);
+ return false;
+ }
+
+ switch (fn)
+ {
+ case FUNC_DISPLAY_CLEAR:
+ result = MAXCmndClear();
+ break;
+ case FUNC_DISPLAY_NUMBER:
+ result = MAXCmndNumber(true);
+ break;
+ case FUNC_DISPLAY_NUMBERNC:
+ result = MAXCmndNumber(false);
+ break;
+ case FUNC_DISPLAY_FLOAT:
+ result = MAXCmndFloat(true);
+ break;
+ case FUNC_DISPLAY_FLOATNC:
+ result = MAXCmndFloat(false);
+ break;
+ case FUNC_DISPLAY_BRIGHTNESS:
+ result = MAXCmndBrightness();
+ break;
+ case FUNC_DISPLAY_RAW:
+ result = MAXCmndRaw();
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXT:
+ result = MAXCmndText(true);
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXTNC:
+ result = MAXCmndText(false);
+ break;
+ case FUNC_DISPLAY_LEVEL:
+ result = MAXCmndLevel();
+ break;
+ case FUNC_DISPLAY_SCROLLTEXT:
+ result = MAXCmndScrollText();
+ break;
+ case FUNC_DISPLAY_SCROLLDELAY:
+ result = MAXCmndScrollDelay();
+ break;
+ case FUNC_DISPLAY_CLOCK:
+ result = MAXCmndClock();
+ break;
+ }
+
+ return result;
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+bool Xdsp16(uint8_t function)
+{
+ bool result = false;
+
+ if (Settings.display_model == XDSP_16)
+ {
+ switch (function)
+ {
+ case FUNC_DISPLAY_MODEL:
+ result = true;
+ break;
+ case FUNC_DISPLAY_INIT_DRIVER:
+ MAXDriverInit(); // init
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXT:
+ case FUNC_DISPLAY_CLEAR:
+ case FUNC_DISPLAY_NUMBER:
+ case FUNC_DISPLAY_FLOAT:
+ case FUNC_DISPLAY_NUMBERNC:
+ case FUNC_DISPLAY_FLOATNC:
+ case FUNC_DISPLAY_RAW:
+ case FUNC_DISPLAY_LEVEL:
+ case FUNC_DISPLAY_SEVENSEG_TEXTNC:
+ case FUNC_DISPLAY_SCROLLTEXT:
+ case FUNC_DISPLAY_SCROLLDELAY:
+ case FUNC_DISPLAY_CLOCK:
+ MAX7219Data.show_clock = false;
+ case FUNC_DISPLAY_BRIGHTNESS:
+ result = MAXMainFunc(function);
+ break;
+ case FUNC_DISPLAY_EVERY_50_MSECOND:
+ if (MAX7219Data.scroll)
+ maxScrollText();
+ if (MAX7219Data.show_clock)
+ maxShowTime();
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_DISPLAY_MAX7219
+#endif // USE_DISPLAY
From 150a8baf2991eef565e42cd2b77226d9fd8ca84a Mon Sep 17 00:00:00 2001
From: Ajith Vasudevan
Date: Fri, 19 Mar 2021 16:07:47 +0530
Subject: [PATCH 02/35] Fixed include for tasmota32 build
---
lib/lib_display/LedControl/src/LedControl.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/lib_display/LedControl/src/LedControl.h b/lib/lib_display/LedControl/src/LedControl.h
index cdfaa1f5a..f8180d07d 100644
--- a/lib/lib_display/LedControl/src/LedControl.h
+++ b/lib/lib_display/LedControl/src/LedControl.h
@@ -27,7 +27,7 @@
#ifndef LedControl_h
#define LedControl_h
-#include
+#include
#if (ARDUINO >= 100)
#include
From d079bdc2fdaa29ee69210ba3b51ade11a82fc1c5 Mon Sep 17 00:00:00 2001
From: Ajith Vasudevan
Date: Fri, 19 Mar 2021 22:30:06 +0530
Subject: [PATCH 03/35] Added MAX7912 to TM1637 driver
---
tasmota/xdsp_15_tm1637.ino | 1036 +++++++++++++++++++++++------------
tasmota/xdsp_16_max7219.ino | 878 -----------------------------
2 files changed, 685 insertions(+), 1229 deletions(-)
delete mode 100644 tasmota/xdsp_16_max7219.ino
diff --git a/tasmota/xdsp_15_tm1637.ino b/tasmota/xdsp_15_tm1637.ino
index 0b507fce5..861f2111d 100644
--- a/tasmota/xdsp_15_tm1637.ino
+++ b/tasmota/xdsp_15_tm1637.ino
@@ -21,7 +21,7 @@
#ifdef USE_DISPLAY_TM1637
/*********************************************************************************************\
This driver enables the display of numbers (both integers and floats) and basic text
- on the inexpensive TM1637- and TM1638-based seven-segment modules.
+ on the inexpensive TM1637-, TM1638- and MAX7219-based seven-segment modules.
Raw segments can also be displayed.
@@ -45,9 +45,17 @@
CLK hardware pin --> "TM1638 CLK"
STB hardware pin --> "TM1638 STB"
+ For MAX7219:
+ Connect the MAX7219 display module's pins to any free GPIOs of the ESP8266 module
+ and assign the pins as follows from Tasmota's GUI:
- Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, set the Display Model to 15
- using the command "DisplayModel 15"
+ DIN hardware pin --> "MAX7219 DIN"
+ CS hardware pin --> "MAX7219 CS"
+ CLK hardware pin --> "MAX7219 CLK"
+
+ Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts,
+ set the Display Model to 15 and Display Mode to 0
+ using the command "Backlog DisplayModel 15 ; DisplayMode 0"
If your display is a TM1637 with 6 digits, set Display Columns to the number of digits your
display has, using the command "DisplayCols 6" and restart the ESP module.
@@ -136,63 +144,83 @@
"DisplayClock 0" // turn off clock
+In addition, setting DisplayMode to 1 shows the time, setting it to 2 shows the date
+and setting it to 3 alternates between time and date.
+
\*********************************************************************************************/
-#define XDSP_15 15
+#define XDSP_15 15
+
+#define CMD_MAX_LEN 55
+#define LEVEL_MIN 0
+#define LEVEL_MAX 100
+#define SCROLL_MAX_LEN 50
+#define POSITION_MIN 0
+#define POSITION_MAX 8
+#define LED_MIN 0
+#define LED_MAX 255
+#define MAX7219_ADDR 0
-#define CMD_MAX_LEN 55
-#define LEVEL_MIN 0
-#define LEVEL_MAX 100
-#define SCROLL_MAX_LEN 50
-#define POSITION_MIN 0
-#define POSITION_MAX 8
-#define LED_MIN 0
-#define LED_MAX 255
#include "SevenSegmentTM1637.h"
#include
+#include
SevenSegmentTM1637 *tm1637display;
TM1638plus *tm1638display;
+LedControl *max7219display;
-enum display_types { TM1637, TM1638 };
+enum display_types
+{
+ TM1637,
+ TM1638,
+ MAX7219
+};
-struct {
+struct
+{
char scroll_text[CMD_MAX_LEN];
char msg[60];
char model_name[8];
uint8_t scroll_delay = 4;
uint8_t scroll_index = 0;
uint8_t iteration = 0;
- uint8_t buttons;
uint8_t display_type = TM1637;
- uint8_t prev_buttons;
bool init_done = false;
bool scroll = false;
bool show_clock = false;
bool clock_24 = false;
- bool LED[8] = {false, false, false, false, false, false, false, false};
} TM1637Data;
/*********************************************************************************************\
* Init function
\*********************************************************************************************/
-void TM1637Init(void) {
- if (PinUsed(GPIO_TM1638CLK) && PinUsed(GPIO_TM1638DIO) && PinUsed(GPIO_TM1638STB)) {
+void TM1637Init(void)
+{
+ if (PinUsed(GPIO_TM1638CLK) && PinUsed(GPIO_TM1638DIO) && PinUsed(GPIO_TM1638STB))
+ {
TM1637Data.display_type = TM1638;
Settings.display_width = 8;
}
- else if (PinUsed(GPIO_TM1637CLK) && PinUsed(GPIO_TM1637DIO)) {
+ else if (PinUsed(GPIO_TM1637CLK) && PinUsed(GPIO_TM1637DIO))
+ {
TM1637Data.display_type = TM1637;
- if ((!Settings.display_width || Settings.display_width > 6)) {
+ if ((!Settings.display_width || Settings.display_width > 6))
+ {
Settings.display_width = 4;
}
}
- else {
+ else if (PinUsed(GPIO_MAX7219DIN) && PinUsed(GPIO_MAX7219CLK) && PinUsed(GPIO_MAX7219CS))
+ {
+ TM1637Data.display_type = MAX7219;
+ Settings.display_width = 8;
+ }
+ else
+ {
return;
}
@@ -200,30 +228,73 @@ void TM1637Init(void) {
Settings.display_cols[0] = Settings.display_width;
Settings.display_height = 1;
Settings.display_rows = Settings.display_height;
+ if(!Settings.display_dimmer || Settings.display_dimmer < 2 || Settings.display_dimmer > 15) Settings.display_dimmer = 8;
- if (TM1637 == TM1637Data.display_type) {
+ if (TM1637 == TM1637Data.display_type)
+ {
strcpy_P(TM1637Data.model_name, PSTR("TM1637"));
tm1637display = new SevenSegmentTM1637(Pin(GPIO_TM1637CLK), Pin(GPIO_TM1637DIO));
tm1637display->begin(Settings.display_width, 1);
}
- else if (TM1638 == TM1637Data.display_type) {
+ else if (TM1638 == TM1637Data.display_type)
+ {
strcpy_P(TM1637Data.model_name, PSTR("TM1638"));
- tm1638display = new TM1638plus(Pin(GPIO_TM1638STB), Pin(GPIO_TM1638CLK), Pin(GPIO_TM1638DIO), true );
+ tm1638display = new TM1638plus(Pin(GPIO_TM1638STB), Pin(GPIO_TM1638CLK), Pin(GPIO_TM1638DIO), true);
tm1638display->displayBegin();
}
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ strcpy_P(TM1637Data.model_name, PSTR("MAX7219"));
+ max7219display = new LedControl(Pin(GPIO_MAX7219DIN), Pin(GPIO_MAX7219CLK), Pin(GPIO_MAX7219CS), 1);
+ max7219display->shutdown(MAX7219_ADDR, false);
+ }
TM1637ClearDisplay();
TM1637Dim();
TM1637Data.init_done = true;
AddLog(LOG_LEVEL_INFO, PSTR("DSP: %s with %d digits"), TM1637Data.model_name, Settings.display_width);
}
+// Function to display specified ascii char at specified position for MAX7219
+void displayMAX7219ASCII(uint8_t pos, char c)
+{
+ pos = 7 - pos;
+ max7219display->setChar(MAX7219_ADDR, pos, c, false);
+}
+
+// Function to display specified ascii char with dot at specified position for MAX7219
+void displayMAX7219ASCIIwDot(uint8_t pos, char c)
+{
+ pos = 7 - pos;
+ max7219display->setChar(MAX7219_ADDR, pos, c, true);
+}
+
+// Function to display raw segments at specified position for MAX7219
+void displayMAX72197Seg(uint8_t pos, uint8_t seg)
+{
+ bool dec_bit = seg & 128;
+ seg = seg << 1;
+ seg = seg | dec_bit;
+ uint8_t NO_OF_BITS = 8;
+ uint8_t reverse_num = 0;
+ for (uint8_t i = 0; i < NO_OF_BITS; i++)
+ {
+ if ((seg & (1 << i)))
+ reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
+ }
+ seg = reverse_num;
+
+ pos = 7 - pos;
+ max7219display->setRow(MAX7219_ADDR, pos, seg);
+}
+
/*********************************************************************************************\
* Displays number without decimal, with/without leading zeros, specifying start-position
* and length, optionally skipping clearing display before displaying the number.
* commands: DisplayNumber num [,position {0-(Settings.display_width-1)} [,leading_zeros {0|1} [,length {1 to Settings.display_width}]]]
* DisplayNumberNC num [,position {0-(Settings.display_width-1)} [,leading_zeros {0|1} [,length {1 to Settings.display_width}]]] // "NC" --> "No Clear"
\*********************************************************************************************/
-bool CmndTM1637Number(bool clear) {
+bool CmndTM1637Number(bool clear)
+{
char sNum[CMD_MAX_LEN];
char sLeadingzeros[CMD_MAX_LEN];
char sPosition[CMD_MAX_LEN];
@@ -236,47 +307,79 @@ bool CmndTM1637Number(bool clear) {
switch (ArgC())
{
- case 4 :
- subStr(sLength, XdrvMailbox.data, ",", 4);
- length = atoi(sLength);
- case 3 :
- subStr(sLeadingzeros, XdrvMailbox.data, ",", 3);
- leadingzeros = atoi(sLeadingzeros);
- case 2 :
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1 :
- subStr(sNum, XdrvMailbox.data, ",", 1);
- num = atof(sNum);
+ case 4:
+ subStr(sLength, XdrvMailbox.data, ",", 4);
+ length = atoi(sLength);
+ case 3:
+ subStr(sLeadingzeros, XdrvMailbox.data, ",", 3);
+ leadingzeros = atoi(sLeadingzeros);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sNum, XdrvMailbox.data, ",", 1);
+ num = atof(sNum);
}
-
- if((position < 0) || (position > (Settings.display_width-1))) position = 0;
+ if ((position < 0) || (position > (Settings.display_width - 1)))
+ position = 0;
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: num %d, pos %d, lead %d, len %d"), num, position, leadingzeros, length);
- if(clear) TM1637ClearDisplay();
+ if (clear)
+ TM1637ClearDisplay();
char txt[30];
snprintf_P(txt, sizeof(txt), PSTR("%d"), num);
- if(!length) length = strlen(txt);
- if((length < 0) || (length > Settings.display_width)) length = Settings.display_width;
+ if (!length)
+ length = strlen(txt);
+ if ((length < 0) || (length > Settings.display_width))
+ length = Settings.display_width;
- char pad = (leadingzeros ? '0': ' ');
+ char pad = (leadingzeros ? '0' : ' ');
uint32_t i = position;
uint8_t rawBytes[1];
- for(; iSettings.display_width) break;
- if(TM1637Data.display_type == TM1637) { rawBytes[0] = tm1637display->encode(pad); tm1637display->printRaw(rawBytes, 1, i); }
- else if(TM1637Data.display_type == TM1638) tm1638display->displayASCII(i, pad);
+ for (; i < position + (length - strlen(txt)); i++)
+ {
+ if (i > Settings.display_width)
+ break;
+ if (TM1637 == TM1637Data.display_type)
+ {
+ rawBytes[0] = tm1637display->encode(pad);
+ tm1637display->printRaw(rawBytes, 1, i);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ tm1638display->displayASCII(i, pad);
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ if (i > 7)
+ break;
+ displayMAX7219ASCII(i, pad);
+ }
}
- for(uint32_t j = 0; i< position + length; i++, j++) {
- if(i>Settings.display_width) break;
- if(txt[j] == 0) break;
- if(TM1637Data.display_type == TM1637) { rawBytes[0] = tm1637display->encode(txt[j]); tm1637display->printRaw(rawBytes, 1, i); }
- else if(TM1637Data.display_type == TM1638) tm1638display->displayASCII(i, txt[j]);
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > Settings.display_width)
+ break;
+ if (txt[j] == 0)
+ break;
+ if (TM1637 == TM1637Data.display_type)
+ {
+ rawBytes[0] = tm1637display->encode(txt[j]);
+ tm1637display->printRaw(rawBytes, 1, i);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ tm1638display->displayASCII(i, txt[j]);
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ if (i > 7)
+ break;
+ if (txt[j] == 0)
+ break;
+ displayMAX7219ASCII(i, txt[j]);
+ }
}
return true;
@@ -288,7 +391,8 @@ bool CmndTM1637Number(bool clear) {
* commands: DisplayFloat num [,position {0-(Settings.display_width-1)} [,precision {0-Settings.display_width} [,length {1 to Settings.display_width}]]]
* DisplayFloatNC num [,position {0-(Settings.display_width-1)} [,precision {0-Settings.display_width} [,length {1 to Settings.display_width}]]] // "NC" --> "No Clear"
\*********************************************************************************************/
-bool CmndTM1637Float(bool clear) {
+bool CmndTM1637Float(bool clear)
+{
char sNum[CMD_MAX_LEN];
char sPrecision[CMD_MAX_LEN];
@@ -302,102 +406,148 @@ bool CmndTM1637Float(bool clear) {
switch (ArgC())
{
- case 4 :
- subStr(sLength, XdrvMailbox.data, ",", 4);
- length = atoi(sLength);
- case 3 :
- subStr(sPrecision, XdrvMailbox.data, ",", 3);
- precision = atoi(sPrecision);
- case 2 :
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1 :
- subStr(sNum, XdrvMailbox.data, ",", 1);
- fnum = atof(sNum);
+ case 4:
+ subStr(sLength, XdrvMailbox.data, ",", 4);
+ length = atoi(sLength);
+ case 3:
+ subStr(sPrecision, XdrvMailbox.data, ",", 3);
+ precision = atoi(sPrecision);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sNum, XdrvMailbox.data, ",", 1);
+ fnum = atof(sNum);
}
+ if ((position < 0) || (position > (Settings.display_width - 1)))
+ position = 0;
+ if ((precision < 0) || (precision > Settings.display_width))
+ precision = Settings.display_width;
- if((position < 0) || (position > (Settings.display_width-1))) position = 0;
- if((precision < 0) || (precision > Settings.display_width)) precision = Settings.display_width;
-
- if(clear) TM1637ClearDisplay();
+ if (clear)
+ TM1637ClearDisplay();
char txt[30];
ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), precision, &fnum);
- if(!length) length = strlen(txt);
- if((length <= 0) || (length > Settings.display_width)) length = Settings.display_width;
+ if (!length)
+ length = strlen(txt);
+ if ((length <= 0) || (length > Settings.display_width))
+ length = Settings.display_width;
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: num %4_f, prec %d, len %d"), &fnum, precision, length);
- if(TM1637Data.display_type == TM1637) {
+ if (TM1637 == TM1637Data.display_type)
+ {
uint8_t rawBytes[1];
- for(uint32_t i=0, j=0; iencode(txt[i]);
- if(txt[i+1] == '.') {
+ if (txt[i + 1] == '.')
+ {
rawBytes[0] = rawBytes[0] | 128;
i++;
length++;
}
- if((j+position) > Settings.display_width) break;
- tm1637display->printRaw(rawBytes, 1, j+position);
+ if ((j + position) > Settings.display_width)
+ break;
+ tm1637display->printRaw(rawBytes, 1, j + position);
}
- } else if(TM1637Data.display_type == TM1638) {
- for(uint32_t i=0, j=0; i 7) break;
- if(txt[i] == 0) break;
- if(txt[i+1] == '.') {
- tm1638display->displayASCIIwDot(j+position, txt[i]);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ for (uint32_t i = 0, j = 0; i < length; i++, j++)
+ {
+ if ((j + position) > 7)
+ break;
+ if (txt[i] == 0)
+ break;
+ if (txt[i + 1] == '.')
+ {
+ tm1638display->displayASCIIwDot(j + position, txt[i]);
i++;
length++;
}
- else tm1638display->displayASCII(j+position, txt[i]);
+ else
+ tm1638display->displayASCII(j + position, txt[i]);
+ }
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ for (uint32_t i = 0, j = 0; i < length; i++, j++)
+ {
+ if ((j + position) > 7)
+ break;
+ if (txt[i] == 0)
+ break;
+ if (txt[i + 1] == '.')
+ {
+ displayMAX7219ASCIIwDot(j + position, txt[i]);
+ i++;
+ length++;
+ }
+ else
+ displayMAX7219ASCII(j + position, txt[i]);
}
}
return true;
}
-
// /*********************************************************************************************\
// * Clears the display
// * Command: DisplayClear
// \*********************************************************************************************/
-bool CmndTM1637Clear(void) {
+bool CmndTM1637Clear(void)
+{
TM1637ClearDisplay();
sprintf(TM1637Data.msg, PSTR("Cleared"));
XdrvMailbox.data = TM1637Data.msg;
return true;
}
-
// /*********************************************************************************************\
// * Clears the display
// \*********************************************************************************************/
-void TM1637ClearDisplay (void) {
- if(TM1637Data.display_type == TM1637) {
- unsigned char arr[] = {0};
- for(int i=0; iprintRaw(arr, 1, i);
- } else if(TM1637Data.display_type == TM1638) {
- for(int i=0; idisplay7Seg(i, 0);
+void TM1637ClearDisplay(void)
+{
+ if (TM1637 == TM1637Data.display_type)
+ {
+ unsigned char arr[] = {0};
+ for (int i = 0; i < Settings.display_width; i++)
+ tm1637display->printRaw(arr, 1, i);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ for (int i = 0; i < Settings.display_width; i++)
+ tm1638display->display7Seg(i, 0);
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ max7219display->clearDisplay(MAX7219_ADDR);
}
}
-
/*********************************************************************************************\
* Display scrolling text
* Command: DisplayTM1637Data.scroll_text text
\*********************************************************************************************/
-bool CmndTM1637ScrollText(void) {
+bool CmndTM1637ScrollText(void)
+{
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: Text %s"), XdrvMailbox.data);
- if(XdrvMailbox.data_len > SCROLL_MAX_LEN) {
+ if (XdrvMailbox.data_len > SCROLL_MAX_LEN)
+ {
snprintf(TM1637Data.msg, sizeof(TM1637Data.msg), PSTR("Text too long. Length should be less than %d"), SCROLL_MAX_LEN);
XdrvMailbox.data = TM1637Data.msg;
return false;
- } else {
+ }
+ else
+ {
snprintf(TM1637Data.scroll_text, sizeof(TM1637Data.scroll_text), PSTR(" "));
snprintf(TM1637Data.scroll_text, sizeof(TM1637Data.scroll_text), PSTR("%s"), XdrvMailbox.data);
TM1637Data.scroll_text[XdrvMailbox.data_len] = 0;
@@ -405,65 +555,85 @@ bool CmndTM1637ScrollText(void) {
TM1637Data.scroll = true;
return true;
}
-
}
-
-
/*********************************************************************************************\
* Sets the scroll delay for scrolling text.
* Command: DisplayTM1637Data.scroll_delay delay {0-15} // default = 4
\*********************************************************************************************/
-bool CmndTM1637ScrollDelay(void) {
- if(ArgC() == 0) {
+bool CmndTM1637ScrollDelay(void)
+{
+ if (ArgC() == 0)
+ {
XdrvMailbox.payload = TM1637Data.scroll_delay;
return true;
}
- if(TM1637Data.scroll_delay<0) TM1637Data.scroll_delay=0;
+ if (TM1637Data.scroll_delay < 0)
+ TM1637Data.scroll_delay = 0;
TM1637Data.scroll_delay = XdrvMailbox.payload;
return true;
}
-
-
/*********************************************************************************************\
* Scrolls a given string. Called every 50ms
\*********************************************************************************************/
-void TM1637ScrollText(void) {
+void TM1637ScrollText(void)
+{
TM1637Data.iteration++;
- if(TM1637Data.scroll_delay) TM1637Data.iteration = TM1637Data.iteration % TM1637Data.scroll_delay;
- else TM1637Data.iteration = 0;
- if(TM1637Data.iteration) return;
+ if (TM1637Data.scroll_delay)
+ TM1637Data.iteration = TM1637Data.iteration % TM1637Data.scroll_delay;
+ else
+ TM1637Data.iteration = 0;
+ if (TM1637Data.iteration)
+ return;
- if(TM1637Data.scroll_index > strlen(TM1637Data.scroll_text)) {
- TM1637Data.scroll= false;
+ if (TM1637Data.scroll_index > strlen(TM1637Data.scroll_text))
+ {
+ TM1637Data.scroll = false;
TM1637Data.scroll_index = 0;
return;
}
uint8_t rawBytes[1];
- for(uint32_t i=0, j=TM1637Data.scroll_index; i< 1 + strlen(TM1637Data.scroll_text); i++, j++) {
- if(i > (Settings.display_width-1)) { break; }
+ for (uint32_t i = 0, j = TM1637Data.scroll_index; i < 1 + strlen(TM1637Data.scroll_text); i++, j++)
+ {
+ if (i > (Settings.display_width - 1))
+ {
+ break;
+ }
rawBytes[0] = tm1637display->encode(TM1637Data.scroll_text[j]);
bool dotSkipped = false;
- if(TM1637Data.scroll_text[j+1] == '.') {
+ if (TM1637Data.scroll_text[j + 1] == '.')
+ {
dotSkipped = true;
rawBytes[0] = rawBytes[0] | 128;
j++;
- } else if(TM1637Data.scroll_text[j] == '^') {
+ }
+ else if (TM1637Data.scroll_text[j] == '^')
+ {
rawBytes[0] = 1 | 2 | 32 | 64;
}
- if(!dotSkipped && TM1637Data.scroll_text[j] == '.') {
+ if (!dotSkipped && TM1637Data.scroll_text[j] == '.')
+ {
j++;
TM1637Data.scroll_index++;
rawBytes[0] = tm1637display->encode(TM1637Data.scroll_text[j]);
}
- if(TM1637Data.scroll_text[j+1] == '.') { rawBytes[0] = rawBytes[0] | 128; }
- if(TM1637Data.display_type == TM1637) {
+ if (TM1637Data.scroll_text[j + 1] == '.')
+ {
+ rawBytes[0] = rawBytes[0] | 128;
+ }
+ if (TM1637 == TM1637Data.display_type)
+ {
tm1637display->printRaw(rawBytes, 1, i);
- } else if(TM1637Data.display_type == TM1638) {
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
tm1638display->display7Seg(i, rawBytes[0]);
}
-
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ displayMAX72197Seg(i, rawBytes[0]);
+ }
}
TM1637Data.scroll_index++;
}
@@ -472,14 +642,16 @@ void TM1637ScrollText(void) {
* Displays a horizontal bar graph. Takes a percentage number (0-100) as input
* Command: DisplayLevel level {0-100}
\*********************************************************************************************/
-bool CmndTM1637Level(void) {
+bool CmndTM1637Level(void)
+{
uint16_t val = XdrvMailbox.payload;
- if((val < LEVEL_MIN) || (val > LEVEL_MAX)) {
+ if ((val < LEVEL_MIN) || (val > LEVEL_MAX))
+ {
Response_P(PSTR("{\"Error\":\"Level should be a number in the range [%d, %d]\"}"), LEVEL_MIN, LEVEL_MAX);
return false;
}
- uint8_t totalBars = 2*Settings.display_width;
+ uint8_t totalBars = 2 * Settings.display_width;
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: TM1637Data.model_name %s CmndTM1637Level totalBars=%d"), TM1637Data.model_name, totalBars);
float barsToDisplay = totalBars * val / 100.0f;
char txt[5];
@@ -492,16 +664,23 @@ bool CmndTM1637Level(void) {
TM1637ClearDisplay();
uint8_t rawBytes[1];
- for(int i=1; i<=numBars; i++) {
- uint8_t digit = (i-1) / 2;
- uint8_t value = (((i%2) == 0) ? 54 : 48);
- if(TM1637Data.display_type == TM1637) {
+ for (int i = 1; i <= numBars; i++)
+ {
+ uint8_t digit = (i - 1) / 2;
+ uint8_t value = (((i % 2) == 0) ? 54 : 48);
+ if (TM1637 == TM1637Data.display_type)
+ {
rawBytes[0] = value;
tm1637display->printRaw(rawBytes, 1, digit);
- } else if(TM1637Data.display_type == TM1638) {
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
tm1638display->display7Seg(digit, value);
}
-
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ displayMAX72197Seg(digit, value);
+ }
}
return true;
}
@@ -514,8 +693,9 @@ bool CmndTM1637Level(void) {
* bit 1 is segment B etc. The function may either set the entire display
* or any desired part using the length and position parameters.
\*********************************************************************************************/
-bool CmndTM1637Raw(void) {
- uint8_t DATA[6] = { 0, 0, 0, 0, 0, 0 };
+bool CmndTM1637Raw(void)
+{
+ uint8_t DATA[6] = {0, 0, 0, 0, 0, 0};
char as[CMD_MAX_LEN];
char bs[CMD_MAX_LEN];
@@ -527,60 +707,76 @@ bool CmndTM1637Raw(void) {
char sLength[CMD_MAX_LEN];
char sPos[CMD_MAX_LEN];
-
uint32_t position = 0;
uint32_t length = 0;
switch (ArgC())
{
- case 8 :
- subStr(fs, XdrvMailbox.data, ",", 8);
- DATA[5] = atoi(fs);
- case 7 :
- subStr(es, XdrvMailbox.data, ",", 7);
- DATA[4] = atoi(es);
- case 6 :
- subStr(ds, XdrvMailbox.data, ",", 6);
- DATA[3] = atoi(ds);
- case 5 :
- subStr(cs, XdrvMailbox.data, ",", 5);
- DATA[2] = atoi(cs);
- case 4 :
- subStr(bs, XdrvMailbox.data, ",", 4);
- DATA[1] = atoi(bs);
- case 3 :
- subStr(as, XdrvMailbox.data, ",", 3);
- DATA[0] = atoi(as);
- case 2 :
- subStr(sLength, XdrvMailbox.data, ",", 2);
- length = atoi(sLength);
- case 1 :
- subStr(sPos, XdrvMailbox.data, ",", 1);
- position = atoi(sPos);
+ case 8:
+ subStr(fs, XdrvMailbox.data, ",", 8);
+ DATA[5] = atoi(fs);
+ case 7:
+ subStr(es, XdrvMailbox.data, ",", 7);
+ DATA[4] = atoi(es);
+ case 6:
+ subStr(ds, XdrvMailbox.data, ",", 6);
+ DATA[3] = atoi(ds);
+ case 5:
+ subStr(cs, XdrvMailbox.data, ",", 5);
+ DATA[2] = atoi(cs);
+ case 4:
+ subStr(bs, XdrvMailbox.data, ",", 4);
+ DATA[1] = atoi(bs);
+ case 3:
+ subStr(as, XdrvMailbox.data, ",", 3);
+ DATA[0] = atoi(as);
+ case 2:
+ subStr(sLength, XdrvMailbox.data, ",", 2);
+ length = atoi(sLength);
+ case 1:
+ subStr(sPos, XdrvMailbox.data, ",", 1);
+ position = atoi(sPos);
}
- if(!length) length = ArgC() - 2;
- if(length < 0 || length > Settings.display_width) length = Settings.display_width;
- if(position < 0 || position > (Settings.display_width-1)) position = 0;
+ if (!length)
+ length = ArgC() - 2;
+ if (length < 0 || length > Settings.display_width)
+ length = Settings.display_width;
+ if (position < 0 || position > (Settings.display_width - 1))
+ position = 0;
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: a %d, b %d, c %d, d %d, e %d, f %d, len %d, pos %d"),
- DATA[0], DATA[1], DATA[2], DATA[3], DATA[4], DATA[5], length, position);
+ DATA[0], DATA[1], DATA[2], DATA[3], DATA[4], DATA[5], length, position);
- if(TM1637Data.display_type == TM1637) {
+ if (TM1637 == TM1637Data.display_type)
+ {
uint8_t rawBytes[1];
- for(uint32_t i=position; i(Settings.display_width-1)) break;
- rawBytes[0] = DATA[i-position];
+ for (uint32_t i = position; i < position + length; i++)
+ {
+ if (i > (Settings.display_width - 1))
+ break;
+ rawBytes[0] = DATA[i - position];
tm1637display->printRaw(rawBytes, 1, i);
}
- } else if(TM1637Data.display_type == TM1638) {
- for(uint32_t i=position; i7) break;
- tm1638display->display7Seg(i, DATA[i-position]);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ for (uint32_t i = position; i < position + length; i++)
+ {
+ if (i > 7)
+ break;
+ tm1638display->display7Seg(i, DATA[i - position]);
+ }
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ for (uint32_t i = position; i < position + length; i++)
+ {
+ if (i > 7)
+ break;
+ displayMAX72197Seg(i, DATA[i - position]);
}
}
-
-
return true;
}
@@ -590,7 +786,8 @@ bool CmndTM1637Raw(void) {
* position parameters without affecting the rest of the display.
* Command: DisplayText text [, position {0-(Settings.display_width-1)} [,length {1 to Settings.display_width}]]
\*********************************************************************************************/
-bool CmndTM1637Text(bool clear) {
+bool CmndTM1637Text(bool clear)
+{
char sString[CMD_MAX_LEN + 1];
char sPosition[CMD_MAX_LEN];
char sLength[CMD_MAX_LEN];
@@ -599,74 +796,126 @@ bool CmndTM1637Text(bool clear) {
switch (ArgC())
{
- case 3 :
- subStr(sLength, XdrvMailbox.data, ",", 3);
- length = atoi(sLength);
- case 2 :
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1 :
- subStr(sString, XdrvMailbox.data, ",", 1);
+ case 3:
+ subStr(sLength, XdrvMailbox.data, ",", 3);
+ length = atoi(sLength);
+ case 2:
+ subStr(sPosition, XdrvMailbox.data, ",", 2);
+ position = atoi(sPosition);
+ case 1:
+ subStr(sString, XdrvMailbox.data, ",", 1);
}
-
- if((position < 0) || (position > (Settings.display_width-1))) position = 0;
+ if ((position < 0) || (position > (Settings.display_width - 1)))
+ position = 0;
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: sString %s, pos %d, len %d"), sString, position, length);
- if(clear) TM1637ClearDisplay();
+ if (clear)
+ TM1637ClearDisplay();
- if(!length) length = strlen(sString);
- if((length < 0) || (length > Settings.display_width)) length = Settings.display_width;
+ if (!length)
+ length = strlen(sString);
+ if ((length < 0) || (length > Settings.display_width))
+ length = Settings.display_width;
uint32_t i = position;
- if(TM1637Data.display_type == TM1637) {
+ if (TM1637 == TM1637Data.display_type)
+ {
uint8_t rawBytes[1];
- for(uint32_t j = 0; i< position + length; i++, j++) {
- if(i > (Settings.display_width-1)) break;
- if(sString[j] == 0) break;
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > (Settings.display_width - 1))
+ break;
+ if (sString[j] == 0)
+ break;
rawBytes[0] = tm1637display->encode(sString[j]);
bool dotSkipped = false;
- if(sString[j+1] == '.') {
+ if (sString[j + 1] == '.')
+ {
dotSkipped = true;
rawBytes[0] = rawBytes[0] | 128;
j++;
- } else if(sString[j] == '^') {
+ }
+ else if (sString[j] == '^')
+ {
rawBytes[0] = 1 | 2 | 32 | 64;
}
- if(!dotSkipped && sString[j] == '.') rawBytes[0] = 128;
+ if (!dotSkipped && sString[j] == '.')
+ rawBytes[0] = 128;
tm1637display->printRaw(rawBytes, 1, i);
}
- } else if(TM1637Data.display_type == TM1638) {
- for(uint32_t j = 0; i< position + length; i++, j++) {
- if(i > 7) break;
- if(sString[j] == 0) break;
- if(sString[j+1] == '.') {
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > 7)
+ break;
+ if (sString[j] == 0)
+ break;
+ if (sString[j + 1] == '.')
+ {
tm1638display->displayASCIIwDot(i, sString[j]);
j++;
- } else if(sString[j] == '^') {
+ }
+ else if (sString[j] == '^')
+ {
tm1638display->display7Seg(i, (1 | 2 | 32 | 64));
- } else tm1638display->displayASCII(i, sString[j]);
+ }
+ else
+ tm1638display->displayASCII(i, sString[j]);
+ }
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ uint8_t rawBytes[1];
+ for (uint32_t j = 0; i < position + length; i++, j++)
+ {
+ if (i > 7)
+ break;
+ if (sString[j] == 0)
+ break;
+ rawBytes[0] = tm1637display->encode(sString[j]);
+ bool dotSkipped = false;
+ if (sString[j + 1] == '.')
+ {
+ dotSkipped = true;
+ rawBytes[0] = rawBytes[0] | 128;
+ j++;
+ }
+ else if (sString[j] == '^')
+ {
+ rawBytes[0] = 1 | 2 | 32 | 64;
+ }
+ if (!dotSkipped && sString[j] == '.')
+ rawBytes[0] = 128;
+ displayMAX72197Seg(i, rawBytes[0]);
}
}
-
return true;
}
-
/*********************************************************************************************\
* Displays a clock.
* Command: DisplayClock 1 // 12-hour format
* DisplayClock 2 // 24-hour format
* DisplayClock 0 // turn off clock and clear
\*********************************************************************************************/
-bool CmndTM1637Clock(void) {
+bool CmndTM1637Clock(void)
+{
TM1637Data.show_clock = XdrvMailbox.payload;
- if(ArgC() == 0) XdrvMailbox.payload = 1;
- if(XdrvMailbox.payload > 1) TM1637Data.clock_24 = true;
- else if(XdrvMailbox.payload == 1) TM1637Data.clock_24 = false;
+ if (ArgC() == 0)
+ XdrvMailbox.payload = 1;
+ if (XdrvMailbox.payload > 1) {
+ TM1637Data.clock_24 = true;
+ XdrvMailbox.payload = 2;
+ } else {
+ TM1637Data.clock_24 = false;
+ XdrvMailbox.payload = 1;
+ }
AddLog(LOG_LEVEL_DEBUG, PSTR("TM7: TM1637Data.show_clock %d, TM1637Data.clock_24 %d"), TM1637Data.show_clock, TM1637Data.clock_24);
@@ -674,110 +923,149 @@ bool CmndTM1637Clock(void) {
return true;
}
-
/*********************************************************************************************\
* refreshes the time if clock is displayed
\*********************************************************************************************/
-void TM1637ShowTime() {
+void TM1637ShowTime()
+{
uint8_t hr = RtcTime.hour;
uint8_t mn = RtcTime.minute;
// uint8_t hr = 1;
// uint8_t mn = 0;
char z = ' ';
- if(TM1637Data.clock_24) {
+ if (TM1637Data.clock_24)
+ {
z = '0';
- } else {
- if(hr > 12) hr -= 12;
- if(hr == 0) hr = 12;
+ }
+ else
+ {
+ if (hr > 12)
+ hr -= 12;
+ if (hr == 0)
+ hr = 12;
}
char tm[5];
- if(hr < 10) {
- if(mn < 10) snprintf(tm, sizeof(tm), PSTR("%c%d0%d"), z, hr, mn);
- else snprintf(tm, sizeof(tm), PSTR("%c%d%d"), z, hr, mn);
- } else {
- if(mn < 10) snprintf(tm, sizeof(tm), PSTR("%d0%d"), hr, mn);
- else snprintf(tm, sizeof(tm), PSTR("%d%d"), hr, mn);
+ if (hr < 10)
+ {
+ if (mn < 10)
+ snprintf(tm, sizeof(tm), PSTR("%c%d0%d"), z, hr, mn);
+ else
+ snprintf(tm, sizeof(tm), PSTR("%c%d%d"), z, hr, mn);
+ }
+ else
+ {
+ if (mn < 10)
+ snprintf(tm, sizeof(tm), PSTR("%d0%d"), hr, mn);
+ else
+ snprintf(tm, sizeof(tm), PSTR("%d%d"), hr, mn);
}
- if(TM1637Data.display_type == TM1637) {
+ if (TM1637 == TM1637Data.display_type)
+ {
uint8_t rawBytes[1];
- for(uint32_t i = 0; i< 4; i++) {
+ for (uint32_t i = 0; i < 4; i++)
+ {
rawBytes[0] = tm1637display->encode(tm[i]);
- if((millis() % 1000) > 500 && (i == 1)) rawBytes[0] = rawBytes[0] | 128;
+ if ((millis() % 1000) > 500 && (i == 1))
+ rawBytes[0] = rawBytes[0] | 128;
tm1637display->printRaw(rawBytes, 1, i);
}
- } else if(TM1637Data.display_type == TM1638) {
- for(uint32_t i = 0; i< 4; i++) {
- if((millis() % 1000) > 500 && (i == 1)) tm1638display->displayASCIIwDot(i, tm[i]);
- else tm1638display->displayASCII(i, tm[i]);
+ }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((millis() % 1000) > 500 && (i == 1))
+ tm1638display->displayASCIIwDot(i, tm[i]);
+ else
+ tm1638display->displayASCII(i, tm[i]);
+ }
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((millis() % 1000) > 500 && (i == 1))
+ displayMAX7219ASCIIwDot(i, tm[i]);
+ else
+ displayMAX7219ASCII(i, tm[i]);
}
}
-
}
/*********************************************************************************************\
* This function is called for all Display functions.
\*********************************************************************************************/
-bool TM1637MainFunc(uint8_t fn) {
+bool TM1637MainFunc(uint8_t fn)
+{
bool result = false;
- if(XdrvMailbox.data_len > CMD_MAX_LEN) {
+ if (XdrvMailbox.data_len > CMD_MAX_LEN)
+ {
Response_P(PSTR("{\"Error\":\"Command text too long. Please limit it to %d characters\"}"), CMD_MAX_LEN);
return false;
}
- switch (fn) {
- case FUNC_DISPLAY_CLEAR:
- result = CmndTM1637Clear();
- break;
- case FUNC_DISPLAY_NUMBER :
- result = CmndTM1637Number(true);
- break;
- case FUNC_DISPLAY_NUMBERNC :
- result = CmndTM1637Number(false);
- break;
- case FUNC_DISPLAY_FLOAT :
- result = CmndTM1637Float(true);
- break;
- case FUNC_DISPLAY_FLOATNC :
- result = CmndTM1637Float(false);
- break;
- case FUNC_DISPLAY_RAW:
- result = CmndTM1637Raw();
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXT:
- result = CmndTM1637Text(true);
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXTNC:
- result = CmndTM1637Text(false);
- break;
- case FUNC_DISPLAY_LEVEL:
- result = CmndTM1637Level();
- break;
- case FUNC_DISPLAY_SCROLLTEXT:
- result = CmndTM1637ScrollText();
- break;
- case FUNC_DISPLAY_SCROLLDELAY:
- result = CmndTM1637ScrollDelay();
- break;
- case FUNC_DISPLAY_CLOCK:
- result = CmndTM1637Clock();
- break;
+ switch (fn)
+ {
+ case FUNC_DISPLAY_CLEAR:
+ result = CmndTM1637Clear();
+ break;
+ case FUNC_DISPLAY_NUMBER:
+ result = CmndTM1637Number(true);
+ break;
+ case FUNC_DISPLAY_NUMBERNC:
+ result = CmndTM1637Number(false);
+ break;
+ case FUNC_DISPLAY_FLOAT:
+ result = CmndTM1637Float(true);
+ break;
+ case FUNC_DISPLAY_FLOATNC:
+ result = CmndTM1637Float(false);
+ break;
+ case FUNC_DISPLAY_RAW:
+ result = CmndTM1637Raw();
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXT:
+ result = CmndTM1637Text(true);
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXTNC:
+ result = CmndTM1637Text(false);
+ break;
+ case FUNC_DISPLAY_LEVEL:
+ result = CmndTM1637Level();
+ break;
+ case FUNC_DISPLAY_SCROLLTEXT:
+ result = CmndTM1637ScrollText();
+ break;
+ case FUNC_DISPLAY_SCROLLDELAY:
+ result = CmndTM1637ScrollDelay();
+ break;
+ case FUNC_DISPLAY_CLOCK:
+ result = CmndTM1637Clock();
+ break;
}
return result;
}
-void TM1637Dim(void) {
+void TM1637Dim(void)
+{
// Settings.display_dimmer = 0 - 15
- uint8_t brightness = Settings.display_dimmer >> 1; // 0 - 7
+ uint8_t brightness = Settings.display_dimmer >> 1; // 0 - 7
- if (TM1637 == TM1637Data.display_type) {
- tm1637display->setBacklight(brightness * 12); // 0 - 84
+ if (TM1637 == TM1637Data.display_type)
+ {
+ tm1637display->setBacklight(brightness * 12); // 0 - 84
}
- else if (TM1637Data.display_type == TM1638) {
- tm1638display->brightness(brightness); // 0 - 7
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ tm1638display->brightness(brightness); // 0 - 7
+ }
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ max7219display->setIntensity(MAX7219_ADDR, brightness); // 0 - 7
}
}
@@ -785,35 +1073,50 @@ void TM1637Dim(void) {
#ifdef USE_DISPLAY_MODES1TO5
-void TM1637Print(char* txt) {
- for (uint32_t i = 0; i < Settings.display_cols[0]; i++) {
- if (TM1637 == TM1637Data.display_type) {
+void TM1637Print(char *txt)
+{
+ for (uint32_t i = 0; i < Settings.display_cols[0]; i++)
+ {
+ if (TM1637 == TM1637Data.display_type)
+ {
uint8_t rawBytes[1];
rawBytes[0] = tm1637display->encode(txt[i]);
-// if ((millis() % 1000) > 500 && (i == 1)) { rawBytes[0] = rawBytes[0] | 128; }
+ // if ((millis() % 1000) > 500 && (i == 1)) { rawBytes[0] = rawBytes[0] | 128; }
tm1637display->printRaw(rawBytes, 1, i);
}
- else if (TM1638 == TM1637Data.display_type) {
-// if ((millis() % 1000) > 500 && (i == 1)) { tm1638display->displayASCIIwDot(i, txt[i]); }
+ else if (TM1638 == TM1637Data.display_type)
+ {
+ // if ((millis() % 1000) > 500 && (i == 1)) { tm1638display->displayASCIIwDot(i, txt[i]); }
tm1638display->displayASCII(i, txt[i]);
}
+ else if (MAX7219 == TM1637Data.display_type)
+ {
+ // if ((millis() % 1000) > 500 && (i == 1)) { tm1638display->displayASCIIwDot(i, txt[i]); }
+ displayMAX7219ASCII(i, txt[i]);
+ }
+
}
}
-void TM1637Center(char* txt) {
- char line[Settings.display_cols[0] +2];
+void TM1637Center(char *txt)
+{
+ char line[Settings.display_cols[0] + 2];
int len = strlen(txt);
int offset = 0;
- if (len >= Settings.display_cols[0]) {
+ if (len >= Settings.display_cols[0])
+ {
len = Settings.display_cols[0];
- } else {
+ }
+ else
+ {
offset = (Settings.display_cols[0] - len) / 2;
}
memset(line, 0x20, Settings.display_cols[0]);
line[Settings.display_cols[0]] = 0;
- for (uint32_t i = 0; i < len; i++) {
- line[offset +i] = txt[i];
+ for (uint32_t i = 0; i < len; i++)
+ {
+ line[offset + i] = txt[i];
}
TM1637Print(line);
}
@@ -845,54 +1148,70 @@ bool TM1637PrintLog(void) {
}
*/
-void TM1637Time(void) {
- char line[Settings.display_cols[0] +1];
+void TM1637Time(void)
+{
+ char line[Settings.display_cols[0] + 1];
- if (Settings.display_cols[0] >= 8) {
+ if (Settings.display_cols[0] >= 8)
+ {
snprintf_P(line, sizeof(line), PSTR("%02d %02d %02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
}
- else if (Settings.display_cols[0] >= 6) {
+ else if (Settings.display_cols[0] >= 6)
+ {
snprintf_P(line, sizeof(line), PSTR("%02d%02d%02d"), RtcTime.hour, RtcTime.minute, RtcTime.second);
}
- else {
+ else
+ {
snprintf_P(line, sizeof(line), PSTR("%02d%02d"), RtcTime.hour, RtcTime.minute);
}
TM1637Center(line);
}
-void TM1637Date(void) {
- char line[Settings.display_cols[0] +1];
+void TM1637Date(void)
+{
+ char line[Settings.display_cols[0] + 1];
- if (Settings.display_cols[0] >= 8) {
- snprintf_P(line, sizeof(line), PSTR("%02d-%02d-%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year -2000);
+ if (Settings.display_cols[0] >= 8)
+ {
+ snprintf_P(line, sizeof(line), PSTR("%02d-%02d-%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year - 2000);
}
- else if (Settings.display_cols[0] >= 6) {
- snprintf_P(line, sizeof(line), PSTR("%02d%02d%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year -2000);
+ else if (Settings.display_cols[0] >= 6)
+ {
+ snprintf_P(line, sizeof(line), PSTR("%02d%02d%02d"), RtcTime.day_of_month, RtcTime.month, RtcTime.year - 2000);
}
- else {
+ else
+ {
snprintf_P(line, sizeof(line), PSTR("%02d%02d"), RtcTime.day_of_month, RtcTime.month);
}
TM1637Center(line);
}
-void TM1637Refresh(void) { // Every second
- if (!disp_power || !Settings.display_mode) { return; } // Mode 0 is User text
+void TM1637Refresh(void)
+{ // Every second
+ if (!disp_power || !Settings.display_mode)
+ {
+ return;
+ } // Mode 0 is User text
- switch (Settings.display_mode) {
- case 1: // Time
+ switch (Settings.display_mode)
+ {
+ case 1: // Time
+ TM1637Time();
+ break;
+ case 2: // Date
+ TM1637Date();
+ break;
+ case 3: // Time
+ if (TasmotaGlobal.uptime % Settings.display_refresh)
+ {
TM1637Time();
- break;
- case 2: // Date
+ }
+ else
+ {
TM1637Date();
- break;
- case 3: // Time
- if (TasmotaGlobal.uptime % Settings.display_refresh) {
- TM1637Time();
- } else {
- TM1637Date();
- }
- break;
-/*
+ }
+ break;
+ /*
case 4: // Mqtt
TM1637PrintLog();
break;
@@ -904,61 +1223,76 @@ void TM1637Refresh(void) { // Every second
}
}
-#endif // USE_DISPLAY_MODES1TO5
+#endif // USE_DISPLAY_MODES1TO5
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
-bool Xdsp15(uint8_t function) {
+bool Xdsp15(uint8_t function)
+{
bool result = false;
- if (FUNC_DISPLAY_INIT_DRIVER == function) {
+ if (FUNC_DISPLAY_INIT_DRIVER == function)
+ {
TM1637Init();
}
- else if (TM1637Data.init_done && (XDSP_15 == Settings.display_model)) {
- switch (function) {
- case FUNC_DISPLAY_EVERY_50_MSECOND:
- if (disp_power && !Settings.display_mode) {
- if (TM1637Data.scroll) { TM1637ScrollText(); }
- if (TM1637Data.show_clock) { TM1637ShowTime(); }
+ else if (TM1637Data.init_done && (XDSP_15 == Settings.display_model))
+ {
+ switch (function)
+ {
+ case FUNC_DISPLAY_EVERY_50_MSECOND:
+ if (disp_power && !Settings.display_mode)
+ {
+ if (TM1637Data.scroll)
+ {
+ TM1637ScrollText();
}
- break;
+ if (TM1637Data.show_clock)
+ {
+ TM1637ShowTime();
+ }
+ }
+ break;
#ifdef USE_DISPLAY_MODES1TO5
- case FUNC_DISPLAY_EVERY_SECOND:
- TM1637Refresh();
- break;
-#endif // USE_DISPLAY_MODES1TO5
- case FUNC_DISPLAY_MODEL:
- result = true;
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXT:
- case FUNC_DISPLAY_CLEAR:
- case FUNC_DISPLAY_NUMBER:
- case FUNC_DISPLAY_FLOAT:
- case FUNC_DISPLAY_NUMBERNC:
- case FUNC_DISPLAY_FLOATNC:
- case FUNC_DISPLAY_RAW:
- case FUNC_DISPLAY_LEVEL:
- case FUNC_DISPLAY_SEVENSEG_TEXTNC:
- case FUNC_DISPLAY_SCROLLTEXT:
- case FUNC_DISPLAY_SCROLLDELAY:
- case FUNC_DISPLAY_CLOCK:
- if (disp_power && !Settings.display_mode) {
- TM1637Data.show_clock = false;
- result = TM1637MainFunc(function);
- }
- break;
- case FUNC_DISPLAY_DIM:
- TM1637Dim();
- break;
- case FUNC_DISPLAY_POWER:
- if (!disp_power) { TM1637ClearDisplay(); }
- break;
+ case FUNC_DISPLAY_EVERY_SECOND:
+ TM1637Refresh();
+ break;
+#endif // USE_DISPLAY_MODES1TO5
+ case FUNC_DISPLAY_MODEL:
+ result = true;
+ break;
+ case FUNC_DISPLAY_SEVENSEG_TEXT:
+ case FUNC_DISPLAY_CLEAR:
+ case FUNC_DISPLAY_NUMBER:
+ case FUNC_DISPLAY_FLOAT:
+ case FUNC_DISPLAY_NUMBERNC:
+ case FUNC_DISPLAY_FLOATNC:
+ case FUNC_DISPLAY_RAW:
+ case FUNC_DISPLAY_LEVEL:
+ case FUNC_DISPLAY_SEVENSEG_TEXTNC:
+ case FUNC_DISPLAY_SCROLLTEXT:
+ case FUNC_DISPLAY_SCROLLDELAY:
+ case FUNC_DISPLAY_CLOCK:
+ if (disp_power && !Settings.display_mode)
+ {
+ TM1637Data.show_clock = false;
+ result = TM1637MainFunc(function);
+ }
+ break;
+ case FUNC_DISPLAY_DIM:
+ TM1637Dim();
+ break;
+ case FUNC_DISPLAY_POWER:
+ if (!disp_power)
+ {
+ TM1637ClearDisplay();
+ }
+ break;
}
}
return result;
}
-#endif // USE_DISPLAY_TM1637
-#endif // USE_DISPLAY
+#endif // USE_DISPLAY_TM1637
+#endif // USE_DISPLAY
diff --git a/tasmota/xdsp_16_max7219.ino b/tasmota/xdsp_16_max7219.ino
deleted file mode 100644
index c8a39b280..000000000
--- a/tasmota/xdsp_16_max7219.ino
+++ /dev/null
@@ -1,878 +0,0 @@
-/*
- xdsp_16_max7219.ino - Support for MAX7219- based seven-segment displays for Tasmota
-
- Copyright (C) 2021 Ajith Vasudevan
-
- 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 .
-*/
-
-#ifdef USE_DISPLAY
-#ifdef USE_DISPLAY_MAX7219
-/*********************************************************************************************\
- This driver enables the display of numbers (both integers and floats) and basic text
- on the inexpensive MAX7219-based seven-segment modules.
-
- Raw segments can also be displayed.
-
- In addition, it is also possible to set brightness (8 levels), clear the display, scroll text,
- display a rudimentary bar graph, and a Clock (12 hr and 24 hr).
-
- To use, compile Tasmota with USE_DISPLAY and USE_DISPLAY_MAX7219, or build the tasmota-display env.
-
- Connect the MAX7219 display module's pins to any free GPIOs of the ESP8266 module
- and assign the pins as follows from Tasmota's GUI:
-
- DIN hardware pin --> "MAX7219 DIN"
- CS hardware pin --> "MAX7219 CS"
- CLK hardware pin --> "MAX7219 CLK"
-
-
- Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, set the Display Model to 16
- using the command "DisplayModel 16"
-
- After the ESP8266/ESP32 module restarts again, the following "Display" commands can be used:
-
-
- DisplayClear
-
- Clears the display, command: "DisplayClear"
-
-
- DisplayNumber num [,position {0-(MAX7219Data.num_digits-1))} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
-
- Clears and then displays number without decimal. command e.g., "DisplayNumber 1234"
- Control 'leading zeros', 'length' and 'position' with "DisplayNumber 1234, , , "
- 'leading zeros' can be 1 or 0 (default), 'length' can be 1 to MAX7219Data.num_digits, 'position' can be 0 (left-most) to MAX7219Data.num_digits (right-most).
- See function description below for more details.
-
- DisplayNumberNC num [,position {0-(MAX7219Data.num_digits-1))} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
-
- Display integer number as above, but without clearing first. e.g., "DisplayNumberNC 1234". Usage is same as above.
-
-
-
- DisplayFloat num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
-
- Clears and then displays float (with decimal point) command e.g., "DisplayFloat 12.34"
- See function description below for more details.
-
-
-
- DisplayFloatNC num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
-
- Displays float (with decimal point) as above, but without clearing first. command e.g., "DisplayFloatNC 12.34"
- See function description below for more details.
-
-
-
- DisplayBrightness num {1-8}
-
- Set brightness (1 to 8) command e.g., "DisplayBrightness 2"
-
-
-
- DisplayRaw position {0-(MAX7219Data.num_digits-1)},length {1 to MAX7219Data.num_digits}, num1 [, num2[, num3[, num4[, ...upto MAX7219Data.num_digits numbers]]]]]
-
- Takes upto MAX7219Data.num_digits comma-separated integers (0-255) and displays raw segments. Each number represents a
- 7-segment digit. Each 8-bit number represents individual segments of a digit.
- For example, the command "DisplayRaw 0, 4, 255, 255, 255, 255" would display "[8.8.8.8.]"
-
-
-
- DisplayText text [, position {0-(MAX7219Data.num_digits-1)} [,length {1 to MAX7219Data.num_digits}]]
-
- Clears and then displays basic text. command e.g., "DisplayText ajith vasudevan"
- Control 'length' and 'position' with "DisplayText , , "
- 'length' can be 1 to MAX7219Data.num_digits, 'position' can be 0 (left-most) to MAX7219Data.num_digits-1 (right-most)
- A caret(^) symbol in the text input is dispayed as the degrees(°) symbol. This is useful for displaying Temperature!
- For example, the command "DisplayText 22.5^" will display "22.5°".
-
-
- DisplayTextNC text [, position {0-MAX7219Data.num_digits-1} [,length {1 to MAX7219Data.num_digits}]]
-
- Clears first, then displays text. Usage is same as above.
-
-
-
- DisplayScrollText text
-
- Displays scrolling text.
-
-
-
- DisplayScrollDelay delay {0-15} // default = 4
-
- Sets the speed of text scroll. Smaller delay = faster scrolling.
-
-
-
- DisplayLevel num {0-100}
-
- Display a horizontal bar graph (0-100) command e.g., "DisplayLevel 50" will display [|||| ]
-
-
-
- DisplayClock 1|2|0
-
- Displays a clock.
- Commands "DisplayClock 1" // 12 hr format
- "DisplayClock 2" // 24 hr format
- "DisplayClock 0" // turn off clock
-
-
-
-\*********************************************************************************************/
-
-#define XDSP_16 16
-
-#define BRIGHTNESS_MIN 1
-#define BRIGHTNESS_MAX 8
-#define CMD_MAX_LEN 55
-#define LEVEL_MIN 0
-#define LEVEL_MAX 100
-#define SCROLL_MAX_LEN 50
-#define POSITION_MIN 0
-#define POSITION_MAX 8
-#define LED_MIN 0
-#define LED_MAX 255
-#define MAX7219_ADDR 0
-
-#include
-
-LedControl *max7219display;
-
-struct
-{
- char scroll_text[CMD_MAX_LEN];
- char msg[60];
- char model_name[8];
- uint8_t num_digits = 4;
- uint8_t scroll_delay = 4;
- uint8_t scroll_index = 0;
- uint8_t iteration = 0;
- uint8_t brightness = 5;
- uint8_t prev_buttons;
-
- bool init_done = false;
- bool scroll = false;
- bool show_clock = false;
- bool clock_24 = false;
-} MAX7219Data;
-
-/*********************************************************************************************\
-* Init function
-\*********************************************************************************************/
-void MAXDriverInit(void)
-{
-
- if (!(PinUsed(GPIO_MAX7219DIN) && PinUsed(GPIO_MAX7219CLK) && PinUsed(GPIO_MAX7219CS)))
- return;
-
- Settings.display_model == XDSP_16;
- MAX7219Data.num_digits = 8;
-
- strcpy(MAX7219Data.model_name, "MAX7219");
- max7219display = new LedControl(Pin(GPIO_MAX7219DIN), Pin(GPIO_MAX7219CLK), Pin(GPIO_MAX7219CS), 1);
- max7219display->shutdown(MAX7219_ADDR, false);
-
- maxClearDisplay();
- MAX7219Data.brightness = (Settings.display_dimmer ? Settings.display_dimmer : MAX7219Data.brightness);
- maxSetBrightness(MAX7219Data.brightness);
- MAX7219Data.init_done = true;
- AddLog(LOG_LEVEL_INFO, PSTR("DSP: %s display driver initialized with %d digits"), MAX7219Data.model_name, MAX7219Data.num_digits);
-}
-
-// Function to display specified ascii char at specified position for MAX7219
-void displayMAX7219ASCII(uint8_t pos, char c)
-{
- pos = 7 - pos;
- max7219display->setChar(MAX7219_ADDR, pos, c, false);
-}
-
-// Function to display specified ascii char with dot at specified position for MAX7219
-void displayMAX7219ASCIIwDot(uint8_t pos, char c)
-{
- pos = 7 - pos;
- max7219display->setChar(MAX7219_ADDR, pos, c, true);
-}
-
-// Function to display raw segments at specified position for MAX7219
-void displayMAX72197Seg(uint8_t pos, uint8_t seg)
-{
- bool dec_bit = seg & 128;
- seg = seg << 1;
- seg = seg | dec_bit;
- uint8_t NO_OF_BITS = 8;
- uint8_t reverse_num = 0;
- for (uint8_t i = 0; i < NO_OF_BITS; i++)
- {
- if ((seg & (1 << i)))
- reverse_num |= 1 << ((NO_OF_BITS - 1) - i);
- }
- seg = reverse_num;
-
- pos = 7 - pos;
- max7219display->setRow(MAX7219_ADDR, pos, seg);
-}
-
-/*********************************************************************************************\
-* Displays number without decimal, with/without leading zeros, specifying start-position
-* and length, optionally skipping clearing display before displaying the number.
-* commands: DisplayNumber num [,position {0-(MAX7219Data.num_digits-1)} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]]
-* DisplayNumberNC num [,position {0-(MAX7219Data.num_digits-1)} [,leading_zeros {0|1} [,length {1 to MAX7219Data.num_digits}]]] // "NC" --> "No Clear"
-\*********************************************************************************************/
-bool MAXCmndNumber(bool clear)
-{
- char sNum[CMD_MAX_LEN];
- char sLeadingzeros[CMD_MAX_LEN];
- char sPosition[CMD_MAX_LEN];
- char sLength[CMD_MAX_LEN];
- uint8_t length = 0;
- bool leadingzeros = false;
- uint8_t position = 0;
-
- uint32_t num = 0;
-
- switch (ArgC())
- {
- case 4:
- subStr(sLength, XdrvMailbox.data, ",", 4);
- length = atoi(sLength);
- case 3:
- subStr(sLeadingzeros, XdrvMailbox.data, ",", 3);
- leadingzeros = atoi(sLeadingzeros);
- case 2:
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1:
- subStr(sNum, XdrvMailbox.data, ",", 1);
- num = atof(sNum);
- }
-
- if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
- position = 0;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: num %d, pos %d, lead %d, len %d"), num, position, leadingzeros, length);
-
- if (clear)
- maxClearDisplay();
-
- char txt[30];
- snprintf_P(txt, sizeof(txt), PSTR("%d"), num);
- if (!length)
- length = strlen(txt);
- if ((length < 0) || (length > MAX7219Data.num_digits))
- length = MAX7219Data.num_digits;
-
- char pad = (leadingzeros ? '0' : ' ');
- uint32_t i = position;
- uint8_t rawBytes[1];
-
- for (; i < position + (length - strlen(txt)); i++)
- {
- if (i > MAX7219Data.num_digits)
- break;
- displayMAX7219ASCII(i, pad);
- }
-
- for (uint32_t j = 0; i < position + length; i++, j++)
- {
- if (i > MAX7219Data.num_digits)
- break;
- if (txt[j] == 0)
- break;
- displayMAX7219ASCII(i, txt[j]);
- }
-
- return true;
-}
-
-/*********************************************************************************************\
-* Displays number with decimal, specifying position, precision and length,
-* optionally skipping clearing display before displaying the number.
-* commands: DisplayFloat num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]]
-* DisplayFloatNC num [,position {0-(MAX7219Data.num_digits-1)} [,precision {0-MAX7219Data.num_digits} [,length {1 to MAX7219Data.num_digits}]]] // "NC" --> "No Clear"
-\*********************************************************************************************/
-bool MAXCmndFloat(bool clear)
-{
-
- char sNum[CMD_MAX_LEN];
- char sPrecision[CMD_MAX_LEN];
- char sPosition[CMD_MAX_LEN];
- char sLength[CMD_MAX_LEN];
- uint8_t length = 0;
- uint8_t precision = MAX7219Data.num_digits;
- uint8_t position = 0;
-
- float fnum = 0.0f;
-
- switch (ArgC())
- {
- case 4:
- subStr(sLength, XdrvMailbox.data, ",", 4);
- length = atoi(sLength);
- case 3:
- subStr(sPrecision, XdrvMailbox.data, ",", 3);
- precision = atoi(sPrecision);
- case 2:
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1:
- subStr(sNum, XdrvMailbox.data, ",", 1);
- fnum = atof(sNum);
- }
-
- if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
- position = 0;
- if ((precision < 0) || (precision > MAX7219Data.num_digits))
- precision = MAX7219Data.num_digits;
-
- if (clear)
- maxClearDisplay();
-
- char txt[30];
- ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), precision, &fnum);
-
- if (!length)
- length = strlen(txt);
- if ((length <= 0) || (length > MAX7219Data.num_digits))
- length = MAX7219Data.num_digits;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: num %4_f, prec %d, len %d"), &fnum, precision, length);
-
- for (uint32_t i = 0, j = 0; i < length; i++, j++)
- {
- if ((j + position) > 7)
- break;
- if (txt[i] == 0)
- break;
- if (txt[i + 1] == '.')
- {
- displayMAX7219ASCIIwDot(j + position, txt[i]);
- i++;
- length++;
- }
- else
- displayMAX7219ASCII(j + position, txt[i]);
- }
- return true;
-}
-
-// /*********************************************************************************************\
-// * Clears the display
-// * Command: DisplayClear
-// \*********************************************************************************************/
-bool MAXCmndClear(void)
-{
- maxClearDisplay();
- sprintf(MAX7219Data.msg, PSTR("Cleared"));
- XdrvMailbox.data = MAX7219Data.msg;
- return true;
-}
-
-// /*********************************************************************************************\
-// * Clears the display
-// \*********************************************************************************************/
-void maxClearDisplay(void)
-{
- max7219display->clearDisplay(MAX7219_ADDR);
-}
-
-/*********************************************************************************************\
-* Display scrolling text
-* Command: DisplayMAX7219Data.scroll_text text
-\*********************************************************************************************/
-bool MAXCmndScrollText(void)
-{
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: Text %s"), XdrvMailbox.data);
-
- if (XdrvMailbox.data_len > SCROLL_MAX_LEN)
- {
- snprintf(MAX7219Data.msg, sizeof(MAX7219Data.msg), PSTR("Text too long. Length should be less than %d"), SCROLL_MAX_LEN);
- XdrvMailbox.data = MAX7219Data.msg;
- return false;
- }
- else
- {
- snprintf(MAX7219Data.scroll_text, sizeof(MAX7219Data.scroll_text), PSTR(" "));
- snprintf(MAX7219Data.scroll_text, sizeof(MAX7219Data.scroll_text), PSTR("%s"), XdrvMailbox.data);
- MAX7219Data.scroll_text[XdrvMailbox.data_len] = 0;
- MAX7219Data.scroll_index = 0;
- MAX7219Data.scroll = true;
- return true;
- }
-}
-
-/*********************************************************************************************\
-* Sets the scroll delay for scrolling text.
-* Command: DisplayMAX7219Data.scroll_delay delay {0-15} // default = 4
-\*********************************************************************************************/
-bool MAXCmndScrollDelay(void)
-{
- if (ArgC() == 0)
- {
- XdrvMailbox.payload = MAX7219Data.scroll_delay;
- return true;
- }
- if (MAX7219Data.scroll_delay < 0)
- MAX7219Data.scroll_delay = 0;
- MAX7219Data.scroll_delay = XdrvMailbox.payload;
- return true;
-}
-
-/*********************************************************************************************\
-* Scrolls a given string. Called every 50ms
-\*********************************************************************************************/
-void maxScrollText(void)
-{
- MAX7219Data.iteration++;
- if (MAX7219Data.scroll_delay)
- MAX7219Data.iteration = MAX7219Data.iteration % MAX7219Data.scroll_delay;
- else
- MAX7219Data.iteration = 0;
- if (MAX7219Data.iteration)
- return;
-
- if (MAX7219Data.scroll_index > strlen(MAX7219Data.scroll_text))
- {
- MAX7219Data.scroll = false;
- MAX7219Data.scroll_index = 0;
- return;
- }
- uint8_t rawBytes[1];
- for (uint32_t i = 0, j = MAX7219Data.scroll_index; i < 1 + strlen(MAX7219Data.scroll_text); i++, j++)
- {
- if (i > (MAX7219Data.num_digits - 1))
- {
- break;
- }
- rawBytes[0] = tm1637display->encode(MAX7219Data.scroll_text[j]);
- bool dotSkipped = false;
- if (MAX7219Data.scroll_text[j + 1] == '.')
- {
- dotSkipped = true;
- rawBytes[0] = rawBytes[0] | 128;
- j++;
- }
- else if (MAX7219Data.scroll_text[j] == '^')
- {
- rawBytes[0] = 1 | 2 | 32 | 64;
- }
- if (!dotSkipped && MAX7219Data.scroll_text[j] == '.')
- {
- j++;
- MAX7219Data.scroll_index++;
- rawBytes[0] = tm1637display->encode(MAX7219Data.scroll_text[j]);
- }
- if (MAX7219Data.scroll_text[j + 1] == '.')
- {
- rawBytes[0] = rawBytes[0] | 128;
- }
- displayMAX72197Seg(i, rawBytes[0]);
- }
- MAX7219Data.scroll_index++;
-}
-
-/*********************************************************************************************\
-* Displays a horizontal bar graph. Takes a percentage number (0-100) as input
-* Command: DisplayLevel level {0-100}
-\*********************************************************************************************/
-bool MAXCmndLevel(void)
-{
- uint16_t val = XdrvMailbox.payload;
- if ((val < LEVEL_MIN) || (val > LEVEL_MAX))
- {
- Response_P(PSTR("{\"Error\":\"Level should be a number in the range [%d, %d]\"}"), LEVEL_MIN, LEVEL_MAX);
- return false;
- }
-
- uint8_t totalBars = 2 * MAX7219Data.num_digits;
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.model_name %s MAXCmndLevel totalBars=%d"), MAX7219Data.model_name, totalBars);
- float barsToDisplay = totalBars * val / 100.0f;
- char txt[5];
- ext_snprintf_P(txt, sizeof(txt), PSTR("%*_f"), 1, &barsToDisplay);
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.model_name %s MAXCmndLevel barsToDisplay=%s"), MAX7219Data.model_name, txt);
- char s[4];
- ext_snprintf_P(s, sizeof(s), PSTR("%0_f"), &barsToDisplay);
- uint8_t numBars = atoi(s);
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: CmndTM1637Level numBars %d"), numBars);
-
- maxClearDisplay();
- uint8_t rawBytes[1];
- for (int i = 1; i <= numBars; i++)
- {
- uint8_t digit = (i - 1) / 2;
- uint8_t value = (((i % 2) == 0) ? 54 : 48);
- displayMAX72197Seg(digit, value);
- }
- return true;
-}
-
-/*********************************************************************************************\
-* Display arbitrary data on the display module
-* Command: DisplayRaw position {0-(MAX7219Data.num_digits-1)},length {1 to MAX7219Data.num_digits}, a [, b[, c[, d[...upto MAX7219Data.num_digits]]]]
-* where a,b,c,d... are upto MAX7219Data.num_digits numbers in the range 0-255, each number (byte)
-* corresponding to a single 7-segment digit. Within each byte, bit 0 is segment A,
-* bit 1 is segment B etc. The function may either set the entire display
-* or any desired part using the length and position parameters.
-\*********************************************************************************************/
-bool MAXCmndRaw(void)
-{
- uint8_t DATA[8] = {0, 0, 0, 0, 0, 0, 0, 0};
-
- char as[CMD_MAX_LEN];
- char bs[CMD_MAX_LEN];
- char cs[CMD_MAX_LEN];
- char ds[CMD_MAX_LEN];
- char es[CMD_MAX_LEN];
- char fs[CMD_MAX_LEN];
- char gs[CMD_MAX_LEN];
- char hs[CMD_MAX_LEN];
-
- char sLength[CMD_MAX_LEN];
- char sPos[CMD_MAX_LEN];
-
- uint32_t position = 0;
- uint32_t length = 0;
-
- switch (ArgC())
- {
- case 10:
- subStr(hs, XdrvMailbox.data, ",", 10);
- DATA[7] = atoi(hs);
- case 9:
- subStr(gs, XdrvMailbox.data, ",", 9);
- DATA[6] = atoi(gs);
- case 8:
- subStr(fs, XdrvMailbox.data, ",", 8);
- DATA[5] = atoi(fs);
- case 7:
- subStr(es, XdrvMailbox.data, ",", 7);
- DATA[4] = atoi(es);
- case 6:
- subStr(ds, XdrvMailbox.data, ",", 6);
- DATA[3] = atoi(ds);
- case 5:
- subStr(cs, XdrvMailbox.data, ",", 5);
- DATA[2] = atoi(cs);
- case 4:
- subStr(bs, XdrvMailbox.data, ",", 4);
- DATA[1] = atoi(bs);
- case 3:
- subStr(as, XdrvMailbox.data, ",", 3);
- DATA[0] = atoi(as);
- case 2:
- subStr(sLength, XdrvMailbox.data, ",", 2);
- length = atoi(sLength);
- case 1:
- subStr(sPos, XdrvMailbox.data, ",", 1);
- position = atoi(sPos);
- }
-
- if (!length)
- length = ArgC() - 2;
- if (length < 0 || length > MAX7219Data.num_digits)
- length = MAX7219Data.num_digits;
- if (position < 0 || position > (MAX7219Data.num_digits - 1))
- position = 0;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: a %d, b %d, c %d, d %d, e %d, f %d, g %d, h %d, len %d, pos %d"),
- DATA[0], DATA[1], DATA[2], DATA[3], DATA[4], DATA[5], DATA[6], DATA[7], length, position);
-
- for (uint32_t i = position; i < position + length; i++)
- {
- if (i > 7)
- break;
- displayMAX72197Seg(i, DATA[i - position]);
- }
-
- return true;
-}
-
-/*********************************************************************************************\
-* Display a given string.
-* Text can be placed at arbitrary location on the display using the length and
-* position parameters without affecting the rest of the display.
-* Command: DisplayText text [, position {0-(MAX7219Data.num_digits-1)} [,length {1 to MAX7219Data.num_digits}]]
-\*********************************************************************************************/
-bool MAXCmndText(bool clear)
-{
- char sString[CMD_MAX_LEN + 1];
- char sPosition[CMD_MAX_LEN];
- char sLength[CMD_MAX_LEN];
- uint8_t length = 0;
- uint8_t position = 0;
-
- switch (ArgC())
- {
- case 3:
- subStr(sLength, XdrvMailbox.data, ",", 3);
- length = atoi(sLength);
- case 2:
- subStr(sPosition, XdrvMailbox.data, ",", 2);
- position = atoi(sPosition);
- case 1:
- subStr(sString, XdrvMailbox.data, ",", 1);
- }
-
- if ((position < 0) || (position > (MAX7219Data.num_digits - 1)))
- position = 0;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: sString %s, pos %d, len %d"), sString, position, length);
-
- if (clear)
- maxClearDisplay();
-
- if (!length)
- length = strlen(sString);
- if ((length < 0) || (length > MAX7219Data.num_digits))
- length = MAX7219Data.num_digits;
-
- uint32_t i = position;
- uint8_t rawBytes[1];
- for (uint32_t j = 0; i < position + length; i++, j++)
- {
- if (i > (MAX7219Data.num_digits - 1))
- break;
- if (sString[j] == 0)
- break;
- rawBytes[0] = tm1637display->encode(sString[j]);
- bool dotSkipped = false;
- if (sString[j + 1] == '.')
- {
- dotSkipped = true;
- rawBytes[0] = rawBytes[0] | 128;
- j++;
- }
- else if (sString[j] == '^')
- {
- rawBytes[0] = 1 | 2 | 32 | 64;
- }
- if (!dotSkipped && sString[j] == '.')
- rawBytes[0] = 128;
- displayMAX72197Seg(i, rawBytes[0]);
- }
-
- return true;
-}
-
-/*********************************************************************************************\
-* Sets brightness of the display.
-* Command: DisplayBrightness {1-8}
-\*********************************************************************************************/
-bool MAXCmndBrightness(void)
-{
-
- uint16_t val = XdrvMailbox.payload;
- if (ArgC() == 0)
- {
- XdrvMailbox.payload = MAX7219Data.brightness;
- return true;
- }
-
- if ((val < BRIGHTNESS_MIN) || (val > BRIGHTNESS_MAX))
- {
- Response_P(PSTR("{\"Error\":\"Brightness should be a number in the range [%d, %d]\"}"), BRIGHTNESS_MIN, BRIGHTNESS_MAX);
- return false;
- }
- MAX7219Data.brightness = val;
- maxSetBrightness(MAX7219Data.brightness);
- return true;
-}
-
-void maxSetBrightness(uint8_t val)
-{
- if ((val < BRIGHTNESS_MIN) || (val > BRIGHTNESS_MAX))
- val = 5;
- Settings.display_dimmer = val;
- max7219display->setIntensity(MAX7219_ADDR, val - 1);
-}
-
-/*********************************************************************************************\
-* Displays a clock.
-* Command: DisplayClock 1 // 12-hour format
-* DisplayClock 2 // 24-hour format
-* DisplayClock 0 // turn off clock and clear
-\*********************************************************************************************/
-bool MAXCmndClock(void)
-{
-
- MAX7219Data.show_clock = XdrvMailbox.payload;
-
- if (ArgC() == 0)
- XdrvMailbox.payload = 1;
- if (XdrvMailbox.payload > 1)
- MAX7219Data.clock_24 = true;
- else if (XdrvMailbox.payload == 1)
- MAX7219Data.clock_24 = false;
-
- AddLog(LOG_LEVEL_DEBUG, PSTR("MAX: MAX7219Data.show_clock %d, MAX7219Data.clock_24 %d"), MAX7219Data.show_clock, MAX7219Data.clock_24);
-
- maxClearDisplay();
- return true;
-}
-
-/*********************************************************************************************\
-* refreshes the time if clock is displayed
-\*********************************************************************************************/
-void maxShowTime()
-{
- uint8_t hr = RtcTime.hour;
- uint8_t mn = RtcTime.minute;
- // uint8_t hr = 1;
- // uint8_t mn = 0;
- char z = ' ';
- if (MAX7219Data.clock_24)
- {
- z = '0';
- }
- else
- {
- if (hr > 12)
- hr -= 12;
- if (hr == 0)
- hr = 12;
- }
-
- char tm[5];
- if (hr < 10)
- {
- if (mn < 10)
- snprintf(tm, sizeof(tm), PSTR("%c%d0%d"), z, hr, mn);
- else
- snprintf(tm, sizeof(tm), PSTR("%c%d%d"), z, hr, mn);
- }
- else
- {
- if (mn < 10)
- snprintf(tm, sizeof(tm), PSTR("%d0%d"), hr, mn);
- else
- snprintf(tm, sizeof(tm), PSTR("%d%d"), hr, mn);
- }
-
- for (uint32_t i = 0; i < 4; i++)
- {
- if ((millis() % 1000) > 500 && (i == 1))
- displayMAX7219ASCIIwDot(i, tm[i]);
- else
- displayMAX7219ASCII(i, tm[i]);
- }
-}
-
-/*********************************************************************************************\
-* This function is called for all Display functions.
-\*********************************************************************************************/
-bool MAXMainFunc(uint8_t fn)
-{
- bool result = false;
-
- if (XdrvMailbox.data_len > CMD_MAX_LEN)
- {
- Response_P(PSTR("{\"Error\":\"Command text too long. Please limit it to %d characters\"}"), CMD_MAX_LEN);
- return false;
- }
-
- switch (fn)
- {
- case FUNC_DISPLAY_CLEAR:
- result = MAXCmndClear();
- break;
- case FUNC_DISPLAY_NUMBER:
- result = MAXCmndNumber(true);
- break;
- case FUNC_DISPLAY_NUMBERNC:
- result = MAXCmndNumber(false);
- break;
- case FUNC_DISPLAY_FLOAT:
- result = MAXCmndFloat(true);
- break;
- case FUNC_DISPLAY_FLOATNC:
- result = MAXCmndFloat(false);
- break;
- case FUNC_DISPLAY_BRIGHTNESS:
- result = MAXCmndBrightness();
- break;
- case FUNC_DISPLAY_RAW:
- result = MAXCmndRaw();
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXT:
- result = MAXCmndText(true);
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXTNC:
- result = MAXCmndText(false);
- break;
- case FUNC_DISPLAY_LEVEL:
- result = MAXCmndLevel();
- break;
- case FUNC_DISPLAY_SCROLLTEXT:
- result = MAXCmndScrollText();
- break;
- case FUNC_DISPLAY_SCROLLDELAY:
- result = MAXCmndScrollDelay();
- break;
- case FUNC_DISPLAY_CLOCK:
- result = MAXCmndClock();
- break;
- }
-
- return result;
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-bool Xdsp16(uint8_t function)
-{
- bool result = false;
-
- if (Settings.display_model == XDSP_16)
- {
- switch (function)
- {
- case FUNC_DISPLAY_MODEL:
- result = true;
- break;
- case FUNC_DISPLAY_INIT_DRIVER:
- MAXDriverInit(); // init
- break;
- case FUNC_DISPLAY_SEVENSEG_TEXT:
- case FUNC_DISPLAY_CLEAR:
- case FUNC_DISPLAY_NUMBER:
- case FUNC_DISPLAY_FLOAT:
- case FUNC_DISPLAY_NUMBERNC:
- case FUNC_DISPLAY_FLOATNC:
- case FUNC_DISPLAY_RAW:
- case FUNC_DISPLAY_LEVEL:
- case FUNC_DISPLAY_SEVENSEG_TEXTNC:
- case FUNC_DISPLAY_SCROLLTEXT:
- case FUNC_DISPLAY_SCROLLDELAY:
- case FUNC_DISPLAY_CLOCK:
- MAX7219Data.show_clock = false;
- case FUNC_DISPLAY_BRIGHTNESS:
- result = MAXMainFunc(function);
- break;
- case FUNC_DISPLAY_EVERY_50_MSECOND:
- if (MAX7219Data.scroll)
- maxScrollText();
- if (MAX7219Data.show_clock)
- maxShowTime();
- break;
- }
- }
- return result;
-}
-
-#endif // USE_DISPLAY_MAX7219
-#endif // USE_DISPLAY
From 7b51da426035f3913c4de559aea875098173b773 Mon Sep 17 00:00:00 2001
From: Ajith Vasudevan
Date: Fri, 19 Mar 2021 22:40:45 +0530
Subject: [PATCH 04/35] Minor fix
---
tasmota/xdrv_13_display.ino | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index ecc2ad7b7..0377e5cf2 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -1930,7 +1930,7 @@ void CmndDisplayText(void)
#ifndef USE_DISPLAY_MODES1TO5
DisplayText();
#else
- if(Settings.display_model == 15 || Settings.display_model == 16) {
+ if(Settings.display_model == 15) {
XdspCall(FUNC_DISPLAY_SEVENSEG_TEXT);
} else if (!Settings.display_mode) {
DisplayText();
From c7eb0451efbac31c51398d14e42ba332732a5808 Mon Sep 17 00:00:00 2001
From: Ajith Vasudevan
Date: Sat, 20 Mar 2021 09:05:19 +0530
Subject: [PATCH 05/35] Minor documentation update
---
tasmota/xdsp_15_tm1637.ino | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tasmota/xdsp_15_tm1637.ino b/tasmota/xdsp_15_tm1637.ino
index 861f2111d..e2c5ae8ea 100644
--- a/tasmota/xdsp_15_tm1637.ino
+++ b/tasmota/xdsp_15_tm1637.ino
@@ -57,10 +57,12 @@
set the Display Model to 15 and Display Mode to 0
using the command "Backlog DisplayModel 15 ; DisplayMode 0"
- If your display is a TM1637 with 6 digits, set Display Columns to the number of digits your
- display has, using the command "DisplayCols 6" and restart the ESP module.
+ If your display is a TM1637 with 6 digits, set Display Width to the number of digits your
+ display has, using the command "DisplayWidth 6".
- After the ESP8266/ESP32 module restarts again, the following "Display" commands can be used:
+ After the ESP8266/ESP32 module restarts again, turn ON the display with the command "Power 1"
+
+ Now, the following "Display" commands can be used:
DisplayClear
From 5efd957b886347eda7f128df10cfe1103b7372d7 Mon Sep 17 00:00:00 2001
From: Xavier MULLER <33861984+localhost61@users.noreply.github.com>
Date: Sat, 20 Mar 2021 15:54:14 +0100
Subject: [PATCH 06/35] Update fr_FR.h
Essentially Neopool strings translation
---
tasmota/language/fr_FR.h | 144 +++++++++++++++++++--------------------
1 file changed, 72 insertions(+), 72 deletions(-)
diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h
index 980b380f7..b62474851 100644
--- a/tasmota/language/fr_FR.h
+++ b/tasmota/language/fr_FR.h
@@ -362,7 +362,7 @@
#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 "Invalid file signature"
+#define D_UPLOAD_ERR_3 "Signature de fichier invalide"
#define D_UPLOAD_ERR_4 "La taille du programme à flasher est plus grande que la taille réelle de la mémoire flash"
#define D_UPLOAD_ERR_5 "Erreur de comparaison du buffer de téléchargement"
#define D_UPLOAD_ERR_6 "Téléchargement échoué. Activer WebLog 3"
@@ -598,7 +598,7 @@
#define D_SENSOR_WS2812 "WS2812"
#define D_SENSOR_DFR562 "MP3 Player"
#define D_SENSOR_IRSEND "IR TX"
-#define D_SENSOR_SWITCH "Inter." // Suffix "1"
+#define D_SENSOR_SWITCH "Inter" // Suffix "1"
#define D_SENSOR_BUTTON "Bouton" // Suffix "1"
#define D_SENSOR_RELAY "Relais" // Suffix "1i"
#define D_SENSOR_LED "LED" // Suffix "1i"
@@ -620,8 +620,8 @@
#define D_SENSOR_SPI_MOSI "SPI MOSI"
#define D_SENSOR_SPI_CLK "SPI CLK"
#define D_SENSOR_BACKLIGHT "RétroÉcl"
-#define D_SENSOR_PMS5003_TX "PMS5003 Tx"
-#define D_SENSOR_PMS5003_RX "PMS5003 Rx"
+#define D_SENSOR_PMS5003_TX "PMS5003 TX"
+#define D_SENSOR_PMS5003_RX "PMS5003 RX"
#define D_SENSOR_SDS0X1_RX "SDS0X1 RX"
#define D_SENSOR_SDS0X1_TX "SDS0X1 TX"
#define D_SENSOR_HPMA_RX "HPMA RX"
@@ -630,14 +630,14 @@
#define D_SENSOR_SBR_TX "SerBr TX"
#define D_SENSOR_SR04_TRIG "SR04 Tri/TX"
#define D_SENSOR_SR04_ECHO "SR04 Ech/RX"
-#define D_SENSOR_SDM72_TX "SDM72 Tx"
-#define D_SENSOR_SDM72_RX "SDM72 Rx"
+#define D_SENSOR_SDM72_TX "SDM72 TX"
+#define D_SENSOR_SDM72_RX "SDM72 RX"
#define D_SENSOR_SDM120_TX "SDMx20 TX"
#define D_SENSOR_SDM120_RX "SDMx20 RX"
#define D_SENSOR_SDM630_TX "SDM630 TX"
#define D_SENSOR_SDM630_RX "SDM630 RX"
-#define D_SENSOR_WE517_TX "WE517 Tx"
-#define D_SENSOR_WE517_RX "WE517 Rx"
+#define D_SENSOR_WE517_TX "WE517 TX"
+#define D_SENSOR_WE517_RX "WE517 RX"
#define D_SENSOR_TM1637_CLK "TM1637 CLK"
#define D_SENSOR_TM1637_DIO "TM1637 DIO"
#define D_SENSOR_TM1638_CLK "TM1638 CLK"
@@ -653,8 +653,8 @@
#define D_SENSOR_RFRECV "RF RX"
#define D_SENSOR_TUYA_TX "Tuya TX"
#define D_SENSOR_TUYA_RX "Tuya RX"
-#define D_SENSOR_MGC3130_XFER "MGC3130 Xfr"
-#define D_SENSOR_MGC3130_RESET "MGC3130 Rst"
+#define D_SENSOR_MGC3130_XFER "MGC3130 XFR"
+#define D_SENSOR_MGC3130_RESET "MGC3130 RST"
#define D_SENSOR_SSPI_MISO "SSPI MISO"
#define D_SENSOR_SSPI_MOSI "SSPI MOSI"
#define D_SENSOR_SSPI_SCLK "SSPI SCLK"
@@ -673,9 +673,9 @@
#define D_SENSOR_HJL_CF "BL0937 CF"
#define D_SENSOR_MCP39F5_TX "MCP39F5 TX"
#define D_SENSOR_MCP39F5_RX "MCP39F5 RX"
-#define D_SENSOR_MCP39F5_RST "MCP39F5 Rst"
-#define D_SENSOR_CSE7761_TX "CSE7761 Tx"
-#define D_SENSOR_CSE7761_RX "CSE7761 Rx"
+#define D_SENSOR_MCP39F5_RST "MCP39F5 RST"
+#define D_SENSOR_CSE7761_TX "CSE7761 TX"
+#define D_SENSOR_CSE7761_RX "CSE7761 RX"
#define D_SENSOR_CSE7766_TX "CSE7766 TX"
#define D_SENSOR_CSE7766_RX "CSE7766 RX"
#define D_SENSOR_PN532_TX "PN532 TX"
@@ -683,8 +683,8 @@
#define D_SENSOR_SM16716_CLK "SM16716 CLK"
#define D_SENSOR_SM16716_DAT "SM16716 DAT"
#define D_SENSOR_SM16716_POWER "SM16716 PWR"
-#define D_SENSOR_P9813_CLK "P9813 Clk"
-#define D_SENSOR_P9813_DAT "P9813 Dat"
+#define D_SENSOR_P9813_CLK "P9813 CLK"
+#define D_SENSOR_P9813_DAT "P9813 DAT"
#define D_SENSOR_MY92X1_DI "MY92x1 DI"
#define D_SENSOR_MY92X1_DCKI "MY92x1 DCKI"
#define D_SENSOR_ARIRFRCV "ALux IrRcv"
@@ -692,14 +692,14 @@
#define D_SENSOR_TXD "Série TX"
#define D_SENSOR_RXD "Série RX"
#define D_SENSOR_ROTARY "Rotary" // Suffix "1A"
-#define D_SENSOR_HRE_CLOCK "HRE Clock"
-#define D_SENSOR_HRE_DATA "HRE Data"
+#define D_SENSOR_HRE_CLOCK "HRE CLK"
+#define D_SENSOR_HRE_DATA "HRE DAT"
#define D_SENSOR_ADE7953_IRQ "ADE7953 IRQ"
#define D_SENSOR_BUZZER "Buzzer"
-#define D_SENSOR_OLED_RESET "OLED Reset"
+#define D_SENSOR_OLED_RESET "OLED RST"
#define D_SENSOR_ZIGBEE_TXD "ZigBee TX"
#define D_SENSOR_ZIGBEE_RXD "ZigBee RX"
-#define D_SENSOR_ZIGBEE_RST "ZigBee Rst"
+#define D_SENSOR_ZIGBEE_RST "ZigBee RST"
#define D_SENSOR_SOLAXX1_TX "SolaxX1 TX"
#define D_SENSOR_SOLAXX1_RX "SolaxX1 RX"
#define D_SENSOR_IBEACON_TX "iBeacon TX"
@@ -710,36 +710,36 @@
#define D_SENSOR_A4988_STP "A4988 STP"
#define D_SENSOR_A4988_ENA "A4988 ENA"
#define D_SENSOR_A4988_MS1 "A4988 MS1"
-#define D_SENSOR_OUTPUT_HI "Output Hi"
-#define D_SENSOR_OUTPUT_LO "Output Lo"
-#define D_SENSOR_AS608_TX "AS608 Tx"
-#define D_SENSOR_AS608_RX "AS608 Rx"
+#define D_SENSOR_OUTPUT_HI "Sortie Hi"
+#define D_SENSOR_OUTPUT_LO "Sortie Lo"
+#define D_SENSOR_AS608_TX "AS608 TX"
+#define D_SENSOR_AS608_RX "AS608 RX"
#define D_SENSOR_DDS2382_TX "DDS238-2 TX"
#define D_SENSOR_DDS2382_RX "DDS238-2 RX"
#define D_SENSOR_DDSU666_TX "DDSU666 TX"
#define D_SENSOR_DDSU666_RX "DDSU666 RX"
-#define D_SENSOR_SM2135_CLK "SM2135 Clk"
-#define D_SENSOR_SM2135_DAT "SM2135 Dat"
+#define D_SENSOR_SM2135_CLK "SM2135 CLK"
+#define D_SENSOR_SM2135_DAT "SM2135 DAT"
#define D_SENSOR_DEEPSLEEP "Hibernation"
#define D_SENSOR_EXS_ENABLE "EXS Enable"
#define D_SENSOR_CLIENT_TX "Esclave TX"
#define D_SENSOR_CLIENT_RX "Esclave RX"
-#define D_SENSOR_CLIENT_RESET "Esclave Rst"
+#define D_SENSOR_CLIENT_RESET "Esclave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
#define D_SENSOR_HM10_RX "HM10 RX"
#define D_SENSOR_HM10_TX "HM10 TX"
-#define D_SENSOR_LE01MR_RX "LE-01MR Rx"
-#define D_SENSOR_LE01MR_TX "LE-01MR Tx"
-#define D_SENSOR_BL0940_RX "BL0940 Rx"
+#define D_SENSOR_LE01MR_RX "LE-01MR RX"
+#define D_SENSOR_LE01MR_TX "LE-01MR TX"
+#define D_SENSOR_BL0940_RX "BL0940 RX"
#define D_SENSOR_CC1101_GDO0 "CC1101 GDO0"
#define D_SENSOR_CC1101_GDO2 "CC1101 GDO2"
-#define D_SENSOR_HRXL_RX "HRXL Rx"
-#define D_SENSOR_DYP_RX "DYP Rx"
-#define D_SENSOR_ELECTRIQ_MOODL "MOODL Tx"
+#define D_SENSOR_HRXL_RX "HRXL RX"
+#define D_SENSOR_DYP_RX "DYP RX"
+#define D_SENSOR_ELECTRIQ_MOODL "MOODL TX"
#define D_SENSOR_AS3935 "AS3935"
#define D_SENSOR_WINDMETER_SPEED "Anémomètre"
-#define D_SENSOR_TELEINFO_RX "TInfo Rx"
+#define D_SENSOR_TELEINFO_RX "TInfo RX"
#define D_SENSOR_TELEINFO_ENABLE "TInfo En"
#define D_SENSOR_LMT01_PULSE "LMT01 Impulsion"
#define D_SENSOR_ADC_INPUT "ADC Entrée"
@@ -765,14 +765,14 @@
#define D_SENSOR_ETH_PHY_POWER "ETH POWER"
#define D_SENSOR_ETH_PHY_MDC "ETH MDC"
#define D_SENSOR_ETH_PHY_MDIO "ETH MDIO"
-#define D_SENSOR_TCP_TXD "TCP Tx"
-#define D_SENSOR_TCP_RXD "TCP Rx"
+#define D_SENSOR_TCP_TXD "TCP TX"
+#define D_SENSOR_TCP_RXD "TCP RX"
#define D_SENSOR_IEM3000_TX "iEM3000 TX"
#define D_SENSOR_IEM3000_RX "iEM3000 RX"
-#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC Tx"
-#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC Rx"
-#define D_SENSOR_PROJECTOR_CTRL_TX "DLP Tx"
-#define D_SENSOR_PROJECTOR_CTRL_RX "DLP Rx"
+#define D_SENSOR_MIEL_HVAC_TX "MiEl HVAC TX"
+#define D_SENSOR_MIEL_HVAC_RX "MiEl HVAC RX"
+#define D_SENSOR_PROJECTOR_CTRL_TX "DLP TX"
+#define D_SENSOR_PROJECTOR_CTRL_RX "DLP RX"
#define D_SENSOR_SHELLY_DIMMER_BOOT0 "SHD Boot 0"
#define D_SENSOR_SHELLY_DIMMER_RST_INV "SHD Reset"
#define D_SENSOR_RC522_RST "RC522 Rst"
@@ -795,12 +795,12 @@
#define D_SENSOR_SDCARD_CS "CarteSD CS"
#define D_SENSOR_WIEGAND_D0 "Wiegand D0"
#define D_SENSOR_WIEGAND_D1 "Wiegand D1"
-#define D_SENSOR_NEOPOOL_TX "NeoPool Tx"
-#define D_SENSOR_NEOPOOL_RX "NeoPool Rx"
+#define D_SENSOR_NEOPOOL_TX "NeoPool TX"
+#define D_SENSOR_NEOPOOL_RX "NeoPool RX"
#define D_SENSOR_VL53L0X_XSHUT "VL53L0X XSHUT"
-#define D_NEW_ADDRESS "Setting address to"
-#define D_OUT_OF_RANGE "Out of Range"
-#define D_SENSOR_DETECTED "detected"
+#define D_NEW_ADDRESS "Positionner l'adresse à"
+#define D_OUT_OF_RANGE "Hors limites"
+#define D_SENSOR_DETECTED "détecté"
// Units
#define D_UNIT_AMPERE "A"
@@ -848,8 +848,8 @@
#define D_UNIT_WATTHOUR "Wh"
#define D_UNIT_WATT_METER_QUADRAT "W/m²"
//SDM220, SDM120, SDM72, LE01MR
-#define D_EXPORT_POWER "Export Power"
-#define D_IMPORT_POWER "Import Power"
+#define D_EXPORT_POWER "Puissance fournie"
+#define D_IMPORT_POWER "Puissance consommée"
#define D_PHASE_ANGLE "Angle de phase"
#define D_IMPORT_ACTIVE "Énergie act conso"
#define D_EXPORT_ACTIVE "Énergie act fournie"
@@ -931,7 +931,7 @@
#define D_SENSOR_BOILER_OT_TX "OpenTherm TX"
// xnrg_15_teleinfo Denky (Teleinfo)
-#define D_CONTRACT "Type contrat"
+#define D_CONTRACT "Type de contrat"
#define D_POWER_LOAD "Charge actuelle"
#define D_CURRENT_TARIFF "Tarif en cours"
#define D_TARIFF "Tarif"
@@ -957,7 +957,7 @@
#define D_FP_FEATUREFAIL "Empreinte trop petite" // 0x07 Failed to generate character file due to the lack of character point or small fingerprint image
#define D_FP_NOMATCH "Le doigt ne correspond pas" // 0x08 Finger doesn't match
#define D_FP_NOTFOUND "Pas de doigt correspondant" // 0x09 Failed to find matching finger
-#define D_FP_ENROLLMISMATCH "Echec de la comparaison" // 0x0A Failed to combine the character files
+#define D_FP_ENROLLMISMATCH "Échec de la comparaison" // 0x0A Failed to combine the character files
#define D_FP_BADLOCATION "Erreur d'indexation" // 0x0B Addressed PageID is beyond the finger library
#define D_FP_DBRANGEFAIL "Modèle invalide" // 0x0C Error when reading template from library or invalid template
#define D_FP_UPLOADFEATUREFAIL "Erreur de transfert" // 0x0D Error when uploading template
@@ -986,42 +986,42 @@
#define D_NEOPOOL_MACH_GENERIC "Generic"
#define D_NEOPOOL_MACH_BAYROL "Bayrol"
#define D_NEOPOOL_MACH_HAY "Hay"
-#define D_NEOPOOL_FILTRATION_MANUAL "Manual" // Filtration modes
+#define D_NEOPOOL_FILTRATION_MANUAL "Manuel" // Filtration modes
#define D_NEOPOOL_FILTRATION_AUTO "Auto"
-#define D_NEOPOOL_FILTRATION_HEATING "Heating"
-#define D_NEOPOOL_FILTRATION_SMART "Smart"
+#define D_NEOPOOL_FILTRATION_HEATING "Chauffage"
+#define D_NEOPOOL_FILTRATION_SMART "Malin"
#define D_NEOPOOL_FILTRATION_INTELLIGENT "Intelligent"
-#define D_NEOPOOL_FILTRATION_BACKWASH "Backwash"
+#define D_NEOPOOL_FILTRATION_BACKWASH "Rétro-lavage"
#define D_NEOPOOL_FILTRATION_NONE "" // Filtration speed level
-#define D_NEOPOOL_FILTRATION_SLOW "slow"
-#define D_NEOPOOL_FILTRATION_MEDIUM "medium"
-#define D_NEOPOOL_FILTRATION_FAST "fast"
+#define D_NEOPOOL_FILTRATION_SLOW "lent"
+#define D_NEOPOOL_FILTRATION_MEDIUM "moyen"
+#define D_NEOPOOL_FILTRATION_FAST "rapide"
#define D_NEOPOOL_TYPE "Type" // Sensor & relais names
#define D_NEOPOOL_REDOX "Redox"
-#define D_NEOPOOL_CHLORINE "Chlorine"
-#define D_NEOPOOL_CONDUCTIVITY "Conductivity"
-#define D_NEOPOOL_IONIZATION "Ionization"
-#define D_NEOPOOL_HYDROLYSIS "Hydrolysis"
-#define D_NEOPOOL_RELAY "Relay"
+#define D_NEOPOOL_CHLORINE "Chlore"
+#define D_NEOPOOL_CONDUCTIVITY "Conductivité"
+#define D_NEOPOOL_IONIZATION "Ionisation"
+#define D_NEOPOOL_HYDROLYSIS "Hydrolyse"
+#define D_NEOPOOL_RELAY "Relais"
#define D_NEOPOOL_RELAY_FILTRATION "Filtration"
-#define D_NEOPOOL_RELAY_LIGHT "Light"
-#define D_NEOPOOL_RELAY_PH_ACID "Acid pump"
-#define D_NEOPOOL_RELAY_PH_BASE "Base pump"
-#define D_NEOPOOL_RELAY_RX "Redox level"
-#define D_NEOPOOL_RELAY_CL "Chlorine pump"
-#define D_NEOPOOL_RELAY_CD "Brine pump"
-#define D_NEOPOOL_TIME "Time"
+#define D_NEOPOOL_RELAY_LIGHT "Lumière"
+#define D_NEOPOOL_RELAY_PH_ACID "Pompe acide"
+#define D_NEOPOOL_RELAY_PH_BASE "Pompe base"
+#define D_NEOPOOL_RELAY_RX "Pompe RedOx"
+#define D_NEOPOOL_RELAY_CL "Pompe Chlore"
+#define D_NEOPOOL_RELAY_CD "Pompe Brome"
+#define D_NEOPOOL_TIME "Durée"
#define D_NEOPOOL_FILT_MODE "Filtration"
#define D_NEOPOOL_POLARIZATION "Pol" // Sensor status
#define D_NEOPOOL_PR_OFF "PrOff"
-#define D_NEOPOOL_SETPOINT_OK "Ok"
-#define D_NEOPOOL_COVER "Cover"
-#define D_NEOPOOL_SHOCK "Shock"
+#define D_NEOPOOL_SETPOINT_OK "OK"
+#define D_NEOPOOL_COVER "Couverture"
+#define D_NEOPOOL_SHOCK "Choc chlore"
#define D_NEOPOOL_ALARM "! "
-#define D_NEOPOOL_LOW "Low"
+#define D_NEOPOOL_LOW "Bas"
#define D_NEOPOOL_FLOW1 "FL1"
#define D_NEOPOOL_FLOW2 "FL2"
-#define D_NEOPOOL_PH_HIGH "too high" // ph Alarms
+#define D_NEOPOOL_PH_HIGH "Trop haut" // ph Alarms
#define D_NEOPOOL_PH_LOW "too low"
#define D_NEOPOOL_PUMP_TIME_EXCEEDED "pump time exceeded"
From 873e096d3a301e3f025db3e59bfb55dfde1f0e61 Mon Sep 17 00:00:00 2001
From: Xavier MULLER <33861984+localhost61@users.noreply.github.com>
Date: Sat, 20 Mar 2021 16:03:56 +0100
Subject: [PATCH 07/35] Update fr_FR.h
... and two missing strings
---
tasmota/language/fr_FR.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h
index b62474851..fdced5059 100644
--- a/tasmota/language/fr_FR.h
+++ b/tasmota/language/fr_FR.h
@@ -1022,7 +1022,7 @@
#define D_NEOPOOL_FLOW1 "FL1"
#define D_NEOPOOL_FLOW2 "FL2"
#define D_NEOPOOL_PH_HIGH "Trop haut" // ph Alarms
-#define D_NEOPOOL_PH_LOW "too low"
-#define D_NEOPOOL_PUMP_TIME_EXCEEDED "pump time exceeded"
+#define D_NEOPOOL_PH_LOW "Trop bas"
+#define D_NEOPOOL_PUMP_TIME_EXCEEDED "durée pompage expirée"
#endif // _LANGUAGE_FR_FR_H_
From 8a6f222b8f6ff5fae92ab4d49dc24de842b171f5 Mon Sep 17 00:00:00 2001
From: sle
Date: Sat, 20 Mar 2021 17:15:42 +0100
Subject: [PATCH 08/35] fix a wrong timing issue, when glitches apear on D0/D1
lines (bitTime wrong)
---
tasmota/xsns_82_wiegand.ino | 119 ++++++++++++++++++------------------
1 file changed, 59 insertions(+), 60 deletions(-)
diff --git a/tasmota/xsns_82_wiegand.ino b/tasmota/xsns_82_wiegand.ino
index 70f87b9c8..0a8edb76a 100644
--- a/tasmota/xsns_82_wiegand.ino
+++ b/tasmota/xsns_82_wiegand.ino
@@ -43,6 +43,7 @@
* - added SetOption123 0-Wiegand UID decimal (default) 1-Wiegand UID hexadecimal
* - added SetOption124 0-all keys up to ending char (# or *) send as one tag by MQTT (default) 1-Keypad every key a single tag
* - added a new realtime testing option emulating a Wiegang reader output on same GPIOs where normally reader is attached. Details below
+ * - fix timing issue when fast glitches are detected on one on the datalines. The interbitgab was too short in that case
\*********************************************************************************************/
#pragma message("**** Wiegand interface enabled ****")
@@ -106,10 +107,12 @@ class Wiegand {
bool WiegandConversion (uint64_t , uint16_t );
void setOutputFormat(void); // fix output HEX format
void HandleKeyPad(void); //handle one tag for multi key strokes
+
static void handleD0Interrupt(void);
static void handleD1Interrupt(void);
static void handleDxInterrupt(int in); // fix #11047
+ static void ClearRFIDBuffer(int);
uint64_t rfid;
uint32_t tagSize;
@@ -142,6 +145,58 @@ volatile bool Wiegand::CodeComplete;
volatile RFID_store Wiegand::rfid_found[WIEGAND_RFID_ARRAY_SIZE];
volatile int Wiegand::currentFoundRFIDcount;
+
+
+void ICACHE_RAM_ATTR Wiegand::ClearRFIDBuffer(int endIndex = WIEGAND_RFID_ARRAY_SIZE) {
+ currentFoundRFIDcount=WIEGAND_RFID_ARRAY_SIZE-endIndex; // clear all buffers
+ for (int i= 0; i < endIndex; i++) {
+ rfid_found[i].RFID=0;
+ rfid_found[i].bitCount=0;
+ }
+}
+void ICACHE_RAM_ATTR Wiegand::handleD1Interrupt() { // Receive a 1 bit. (D0=high & D1=low)
+ handleDxInterrupt(1);
+}
+
+void ICACHE_RAM_ATTR Wiegand::handleD0Interrupt() { // Receive a 0 bit. (D0=low & D1=high)
+ handleDxInterrupt(0);
+}
+
+void ICACHE_RAM_ATTR Wiegand::handleDxInterrupt(int in) {
+ unsigned long curTime = micros(); // to be sure I will use micros() instead of millis() overflow is handle by using the minus operator to compare
+ unsigned long diffTime= curTime - lastFoundTime;
+ if ( (diffTime > CodeGapTime) && (bitCount > 0)) {
+ // previous RFID tag (key pad numer)is complete. Will be detected by the code ending gap
+ // one bit will take the time of impulse_time + impulse_gap_time. it (bitTime) will be recalculated each time an impulse is detected
+ // the devices will add some inter_code_gap_time to separate codes this will be much longer than the bit_time. (WIEGAND_CODE_GAP_FACTOR)
+ // unfortunately there's no timing defined for Wiegand. On my test reader the impulse time = 125 µs impulse gap time = 950 µs.
+ if (currentFoundRFIDcount < WIEGAND_RFID_ARRAY_SIZE) { // when reaching the end of rfid buffer we will overwrite the last one.
+ currentFoundRFIDcount++;
+ }
+ // start a new tag
+ rfidBuffer = 0;
+ bitCount = 0;
+ FirstBitTimeStamp = 0;
+ }
+
+ if (in == 0) { rfidBuffer = rfidBuffer << 1; } // Receive a 0 bit. (D0=low & D1=high): Leftshift the 0 bit is now at the end of rfidBuffer
+ else if (in == 1) {rfidBuffer = (rfidBuffer << 1) | 1; } // Receive a 1 bit. (D0=high & D1=low): Leftshift + 1 bit
+ else { return; } // (in==3) called by ScanForTag to get the last tag, because the interrupt handler is no longer called after receiving the last bit
+
+ bitCount++;
+ if (bitCount == 1) { // first bit was detected
+ FirstBitTimeStamp = (curTime != 0) ? curTime : 1; // accept 1µs differenct to avoid a miss the first timestamp if curTime is 0.
+ }
+ else if (bitCount == 2) { // only calculate once per RFID tag, but restrict to values, which are in within a plausible range
+ bitTime = ((diffTime > (WIEGAND_BIT_TIME_DEFAULT/4)) && (diffTime < (4*WIEGAND_BIT_TIME_DEFAULT))) ? diffTime : WIEGAND_BIT_TIME_DEFAULT;
+ CodeGapTime = WIEGAND_CODE_GAP_FACTOR * bitTime;
+ }
+ //save current rfid in array otherwise we will never see the last found tag
+ rfid_found[currentFoundRFIDcount].RFID=rfidBuffer;
+ rfid_found[currentFoundRFIDcount].bitCount= bitCount;
+ lastFoundTime = curTime; // Last time a bit was detected
+}
+
Wiegand::Wiegand() {
rfid = 0;
lastFoundTime = 0;
@@ -154,67 +209,12 @@ Wiegand::Wiegand() {
FirstBitTimeStamp = 0;
CodeGapTime = WIEGAND_CODE_GAP_FACTOR * bitTime;
CodeComplete = false;
- currentFoundRFIDcount=0;
- for (int i=0; i < WIEGAND_RFID_ARRAY_SIZE; i++ )
- {
- rfid_found[i].RFID=0;
- rfid_found[i].bitCount=0;
- }
+ ClearRFIDBuffer();
outFormat="u"; // standard output format decimal
mqttRFIDKeypadBuffer = 0;
webRFIDKeypadBuffer = 0;
}
-void ICACHE_RAM_ATTR Wiegand::handleD1Interrupt() { // Receive a 1 bit. (D0=high & D1=low)
- handleDxInterrupt(1);
-}
-
-void ICACHE_RAM_ATTR Wiegand::handleD0Interrupt() { // Receive a 0 bit. (D0=low & D1=high)
- handleDxInterrupt(0);
-}
-
-void ICACHE_RAM_ATTR Wiegand::handleDxInterrupt(int in) {
-
- unsigned long curTime = micros(); // to be sure I will use micros() instead of millis() overflow is handle by using the minus operator to compare
- unsigned long diffTime= curTime - lastFoundTime;
- if (diffTime > 3000000 ) { //cancel noisy bits in buffer and start a new tag
- rfidBuffer = 0;
- bitCount = 0;
- FirstBitTimeStamp = 0;
- }
- if ( (diffTime > CodeGapTime) && (bitCount > 0)) {
- // previous RFID tag (key pad numer)is complete. Will be detected by the code ending gap
- // one bit will take the time of impulse_time + impulse_gap_time. it (bitTime) will be recalculated each time an impulse is detected
- // the devices will add some inter_code_gap_time to separate codes this will be much longer than the bit_time. (WIEGAND_CODE_GAP_FACTOR)
- // unfortunately there's no timing defined for Wiegang. On my test reader the impulse time = 125 µs impulse gap time = 950 µs.
- if (currentFoundRFIDcount < WIEGAND_RFID_ARRAY_SIZE) { // when reaching the end of rfid buffer we will overwrite the last one.
- currentFoundRFIDcount++;
- }
- // start a new tag
- rfidBuffer = 0;
- bitCount = 0;
- FirstBitTimeStamp = 0;
- }
- if (in ==3) {// called by ScanForTag to get the last tag, because the interrupt handler is no longer called after receiving the last bit
- return;
- }
- if (in == 0) { rfidBuffer = rfidBuffer << 1; } // Receive a 0 bit. (D0=low & D1=high): Leftshift the 0 bit is now at the end of rfidBuffer
- else {rfidBuffer = (rfidBuffer << 1) | 1; } // Receive a 1 bit. (D0=high & D1=low): Leftshift + 1 bit
-
- bitCount++;
- if (bitCount == 1) { // first bit was detected
- FirstBitTimeStamp = (curTime != 0) ? curTime : 1; // accept 1µs differenct to avoid a miss the first timestamp if curTime is 0.
- }
- else if (bitCount == 2) { // only calculate once per RFID tag
- bitTime = diffTime; //calc maximum current length of one bit
- CodeGapTime = WIEGAND_CODE_GAP_FACTOR * bitTime;
- }
- //save current rfid in array otherwise we will never see the last found tag
- rfid_found[currentFoundRFIDcount].RFID=rfidBuffer;
- rfid_found[currentFoundRFIDcount].bitCount= bitCount;
- lastFoundTime = curTime; // Last time a bit was detected
-}
-
void Wiegand::Init() {
isInit = false;
if (PinUsed(GPIO_WIEGAND_D0) && PinUsed(GPIO_WIEGAND_D1)) { // Only start, if the Wiegang pins are selected
@@ -400,7 +400,7 @@ void Wiegand::ScanForTag() {
uint64_t oldTag = rfid;
bool validKey = WiegandConversion(rfid_found[i].RFID, rfid_found[i].bitCount);
#if (DEV_WIEGAND_TEST_MODE)>0
- AddLog(LOG_LEVEL_INFO, PSTR("WIE: Previous tag %llu"), oldTag);
+ AddLog(LOG_LEVEL_INFO, PSTR("WIE: ValidKey: %d Previous tag %llu"), validKey, oldTag);
#endif // DEV_WIEGAND_TEST_MODE>0
if (validKey) { // Only in case of valid key do action. Issue#10585
HandleKeyPad(); //support one tag for multi key input
@@ -412,15 +412,14 @@ void Wiegand::ScanForTag() {
MqttPublishTeleSensor();
}
}
- rfid_found[i].RFID=0;
- rfid_found[i].bitCount=0;
}
}
if (currentFoundRFIDcount > lastFoundRFIDcount) {
// if that happens: we need to move the id found during the loop to top of the array
// and correct the currentFoundRFIDcount
+ AddLog(LOG_LEVEL_INFO, PSTR("WIE: ScanForTag() %lu tags added while working on buffer"), (currentFoundRFIDcount-lastFoundRFIDcount));
}
- currentFoundRFIDcount=0; //reset array
+ ClearRFIDBuffer(); //reset array
#if (DEV_WIEGAND_TEST_MODE)>0
AddLog(LOG_LEVEL_INFO, PSTR("WIE: ScanForTag() time elapsed %lu"), (micros() - startTime));
#endif
From 85dbd6e6fd2ee0d457780efade907e2ef0201d35 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sat, 20 Mar 2021 17:32:09 +0100
Subject: [PATCH 09/35] Fix Sonoff Dual R3 template
---
tasmota/xnrg_19_cse7761.ino | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tasmota/xnrg_19_cse7761.ino b/tasmota/xnrg_19_cse7761.ino
index 98ad863bb..71594969f 100644
--- a/tasmota/xnrg_19_cse7761.ino
+++ b/tasmota/xnrg_19_cse7761.ino
@@ -21,7 +21,7 @@
#ifdef USE_CSE7761
/*********************************************************************************************\
* CSE7761 - Energy (Sonoff Dual R3 Pow)
- * {"NAME":"Sonoff Dual R3","GPIO":[0,0,1,0,0,0,3232,3200,0,0,225,0,0,0,0,0,0,0,0,0,1,7296,7328,224,0,0,0,0,160,161,0,0,0,0,0,0],"FLAG":0,"BASE":1}
+ * {"NAME":"Sonoff Dual R3","GPIO":[32,0,0,0,0,0,0,0,0,576,225,0,0,0,0,0,0,0,0,0,0,7296,7328,224,0,0,0,0,160,161,0,0,0,0,0,0],"FLAG":0,"BASE":1}
*
* Based on datasheet from ChipSea and analysing serial data
* See https://github.com/arendst/Tasmota/discussions/10793
From 70b7e2fc2a76b088e49372626fba79132a04ba91 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sat, 20 Mar 2021 18:11:52 +0100
Subject: [PATCH 10/35] Restore DisplaySize code
---
tasmota/xdrv_13_display.ino | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index 0377e5cf2..cb9e29936 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -1864,9 +1864,11 @@ void CmndDisplayScrollText(void)
void CmndDisplaySize(void)
{
- Settings.display_size = XdrvMailbox.payload;
- if (renderer) renderer->setTextSize(Settings.display_size);
- //else DisplaySetSize(Settings.display_size);
+ if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) {
+ Settings.display_size = XdrvMailbox.payload;
+ if (renderer) renderer->setTextSize(Settings.display_size);
+ //else DisplaySetSize(Settings.display_size);
+ }
ResponseCmndNumber(Settings.display_size);
}
From 9116c9848ad7fc53514ba488f6a44000df0ecdec Mon Sep 17 00:00:00 2001
From: Stephan Hadinger
Date: Sat, 20 Mar 2021 18:44:35 +0100
Subject: [PATCH 11/35] Berry milestone March 20
---
lib/lib_i2c/MPU6886/src/MPU6886.cpp | 20 +-
lib/lib_i2c/MPU6886/src/MPU6886.h | 6 +-
.../Berry-0.1.10/src/port/be_energylib.c | 2 +-
.../Berry-0.1.10/src/port/be_tasmotalib.c | 33 +-
.../Berry-0.1.10/src/port/be_wirelib.c | 14 +-
.../Berry-0.1.10/src/port/berry_conf.h | 14 +-
tasmota/berry/denky.be | 6 +-
tasmota/berry/tasmota.be | 211 --------
tasmota/my_user_config.h | 5 +
tasmota/support_esp.ino | 11 +
tasmota/xdrv_52_0_berry_struct.ino | 62 +++
tasmota/xdrv_52_3_berry_tasmota.ino | 47 +-
tasmota/xdrv_52_3_berry_wire.ino | 67 ++-
tasmota/xdrv_52_7_berry_embedded.ino | 160 ++++--
tasmota/xdrv_52_9_berry.ino | 508 ++++++++++++++----
15 files changed, 757 insertions(+), 409 deletions(-)
delete mode 100644 tasmota/berry/tasmota.be
create mode 100644 tasmota/xdrv_52_0_berry_struct.ino
diff --git a/lib/lib_i2c/MPU6886/src/MPU6886.cpp b/lib/lib_i2c/MPU6886/src/MPU6886.cpp
index 9ae45461d..8774d09f7 100755
--- a/lib/lib_i2c/MPU6886/src/MPU6886.cpp
+++ b/lib/lib_i2c/MPU6886/src/MPU6886.cpp
@@ -4,24 +4,24 @@
void MPU6886::I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer){
- myWire.beginTransmission(driver_Addr);
- myWire.write(start_Addr);
- myWire.endTransmission(false);
+ myWire->beginTransmission(driver_Addr);
+ myWire->write(start_Addr);
+ myWire->endTransmission(false);
uint8_t i = 0;
- myWire.requestFrom(driver_Addr,number_Bytes);
+ myWire->requestFrom(driver_Addr,number_Bytes);
//! Put read results in the Rx buffer
- while (myWire.available()) {
- read_Buffer[i++] = myWire.read();
+ while (myWire->available()) {
+ read_Buffer[i++] = myWire->read();
}
}
void MPU6886::I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer){
- myWire.beginTransmission(driver_Addr);
- myWire.write(start_Addr);
- myWire.write(*write_Buffer);
- myWire.endTransmission();
+ myWire->beginTransmission(driver_Addr);
+ myWire->write(start_Addr);
+ myWire->write(*write_Buffer);
+ myWire->endTransmission();
}
diff --git a/lib/lib_i2c/MPU6886/src/MPU6886.h b/lib/lib_i2c/MPU6886/src/MPU6886.h
index 025f71587..b4a5541e0 100755
--- a/lib/lib_i2c/MPU6886/src/MPU6886.h
+++ b/lib/lib_i2c/MPU6886/src/MPU6886.h
@@ -71,9 +71,9 @@ class MPU6886 {
public:
MPU6886(void) {};
#ifdef ESP32
- void setBus(uint32_t _bus) { myWire = _bus ? Wire1 : Wire; };
+ void setBus(uint32_t _bus) { myWire = _bus ? &Wire1 : &Wire; };
#else
- void setBus(uint32_t _bus) { myWire = Wire; };
+ void setBus(uint32_t _bus) { myWire = &Wire; };
#endif
int Init(void);
void getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az);
@@ -93,7 +93,7 @@ class MPU6886 {
// void getAhrsData(float *pitch,float *roll,float *yaw);
public:
- TwoWire & myWire = Wire; // default to Wire (bus 0)
+ TwoWire * myWire = &Wire; // default to Wire (bus 0)
float aRes, gRes;
private:
diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c b/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c
index 444133852..020766460 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c
+++ b/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c
@@ -19,7 +19,7 @@ be_define_native_module(energy, NULL);
#else
/* @const_object_info_begin
module tasmota (scope: global, depend: 1) {
- getfreeheap, func(l_getFreeHeap)
+ get_free_heap, func(l_getFreeHeap)
}
@const_object_info_end */
#include "../generate/be_fixed_tasmota.h"
diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c b/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c
index 17b09a219..713effefd 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c
+++ b/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c
@@ -27,6 +27,8 @@ extern int l_getpower(bvm *vm);
extern int l_setlight(bvm *vm);
extern int l_setpower(bvm *vm);
+extern int l_i2cenabled(bvm *vm);
+
// #if !BE_USE_PRECOMPILED_OBJECT
#if 1 // TODO we will do pre-compiled later
// Class definition
@@ -39,27 +41,30 @@ void be_load_tasmota_ntvlib(bvm *vm)
{ "_rules", NULL },
{ "_timers", NULL },
{ "_cmd", NULL },
- { "getfreeheap", l_getFreeHeap },
+ { "_drivers", NULL },
+ { "get_free_heap", l_getFreeHeap },
{ "publish", l_publish },
{ "cmd", l_cmd },
- { "getoption", l_getoption },
+ { "get_option", l_getoption },
{ "millis", l_millis },
- { "timereached", l_timereached },
+ { "time_reached", l_timereached },
{ "yield", l_yield },
{ "delay", l_delay },
- { "scaleuint", l_scaleuint },
+ { "scale_uint", l_scaleuint },
- { "respcmnd", l_respCmnd },
- { "respcmndstr", l_respCmndStr },
- { "respcmnd_done", l_respCmndDone },
- { "respcmnd_error", l_respCmndError },
- { "respcmnd_failed", l_respCmndFailed },
+ { "resp_cmnd", l_respCmnd },
+ { "resp_cmnd_str", l_respCmndStr },
+ { "resp_cmnd_done", l_respCmndDone },
+ { "resp_cmnd_error", l_respCmndError },
+ { "resp_cmnd_failed", l_respCmndFailed },
{ "resolvecmnd", l_resolveCmnd },
- { "getlight", l_getlight },
- { "getpower", l_getpower },
- { "setlight", l_setlight },
- { "setpower", l_setpower },
+ { "get_light", l_getlight },
+ { "get_power", l_getpower },
+ { "set_light", l_setlight },
+ { "set_power", l_setpower },
+
+ { "i2c_enabled", l_i2cenabled },
{ NULL, NULL }
};
@@ -69,7 +74,7 @@ void be_load_tasmota_ntvlib(bvm *vm)
#else
/* @const_object_info_begin
module tasmota (scope: global, depend: 1) {
- getfreeheap, func(l_getFreeHeap)
+ get_free_heap, func(l_getFreeHeap)
}
@const_object_info_end */
#include "../generate/be_fixed_tasmota.h"
diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
index 07581b2a4..b6a81c469 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
+++ b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
@@ -20,6 +20,9 @@ extern int b_wire_scan(bvm *vm);
extern int b_wire_validwrite(bvm *vm);
extern int b_wire_validread(bvm *vm);
+extern int b_wire_readbytes(bvm *vm);
+extern int b_wire_writebytes(bvm *vm);
+extern int b_wire_detect(bvm *vm);
// #if !BE_USE_PRECOMPILED_OBJECT
#if 1 // TODO we will do pre-compiled later
@@ -28,15 +31,18 @@ void be_load_wirelib(bvm *vm)
static const bnfuncinfo members[] = {
{ "_bus", NULL }, // bus number
{ "init", b_wire_init },
- { "_begintransmission", b_wire_begintransmission },
- { "_endtransmission", b_wire_endtransmission },
- { "_requestfrom", b_wire_requestfrom },
+ { "_begin_transmission", b_wire_begintransmission },
+ { "_end_transmission", b_wire_endtransmission },
+ { "_request_from", b_wire_requestfrom },
{ "_available", b_wire_available },
{ "_write", b_wire_write },
{ "_read", b_wire_read },
{ "scan", b_wire_scan },
{ "write", b_wire_validwrite },
{ "read", b_wire_validread },
+ { "read_bytes", b_wire_validread },
+ { "write_bytes", b_wire_validread },
+ { "detect", b_wire_detect },
{ NULL, NULL }
};
@@ -45,7 +51,7 @@ void be_load_wirelib(bvm *vm)
#else
/* @const_object_info_begin
module tasmota (scope: global, depend: 1) {
- getfreeheap, func(l_getFreeHeap)
+ get_free_heap, func(l_getFreeHeap)
}
@const_object_info_end */
#include "../generate/be_fixed_tasmota.h"
diff --git a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
index e3720b4d1..57fbe6208 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
+++ b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
@@ -162,11 +162,21 @@
* are not required.
* The default is to use the functions in the standard library.
**/
+#ifdef USE_BERRY_PSRAM
+ extern void *special_malloc(uint32_t size);
+ extern void *special_realloc(void *ptr, size_t size);
+ #define BE_EXPLICIT_MALLOC special_malloc
+ #define BE_EXPLICIT_REALLOC special_realloc
+#else
+ #define BE_EXPLICIT_MALLOC malloc
+ #define BE_EXPLICIT_REALLOC realloc
+#endif // USE_BERRY_PSRAM
+
#define BE_EXPLICIT_ABORT abort
#define BE_EXPLICIT_EXIT exit
-#define BE_EXPLICIT_MALLOC malloc
+// #define BE_EXPLICIT_MALLOC malloc
#define BE_EXPLICIT_FREE free
-#define BE_EXPLICIT_REALLOC realloc
+// #define BE_EXPLICIT_REALLOC realloc
/* Macro: be_assert
* Berry debug assertion. Only enabled when BE_DEBUG is active.
diff --git a/tasmota/berry/denky.be b/tasmota/berry/denky.be
index bcec51407..416e26078 100644
--- a/tasmota/berry/denky.be
+++ b/tasmota/berry/denky.be
@@ -10,11 +10,11 @@ runcolor = nil
def runcolor()
var pwr = energy.read().find('activepower',0)
print(pwr)
- var red = tasmota.scaleuint(int(pwr), 0, 2500, 0, 255)
+ var red = tasmota.scale_uint(int(pwr), 0, 2500, 0, 255)
var green = 255 - red
var channels = [red, green, 0]
- tasmota.setlight({"channels":channels, "bri":64, "power":true})
- tasmota.settimer(2000, runcolor)
+ tasmota.set_light({"channels":channels, "bri":64, "power":true})
+ tasmota.set_timer(2000, runcolor)
end
#- run animation -#
diff --git a/tasmota/berry/tasmota.be b/tasmota/berry/tasmota.be
deleted file mode 100644
index 213eb0be0..000000000
--- a/tasmota/berry/tasmota.be
+++ /dev/null
@@ -1,211 +0,0 @@
-import json import string
-tasmota = module("tasmota")
-def log(m) print(m) end
-def save() end
-
-#######
-import string
-import json
-import gc
-import tasmota
-#// import alias
-import tasmota as t
-
-def charsinstring(s,c)
- for i:0..size(s)-1
- for j:0..size(c)-1
- if s[i] == c[j] return i end
- end
- end
- return -1
-end
-
-###
-class Tasmota
- var _op, _operators, _rules
- def init()
- self._operators = "=<>!|"
- self._op = [
- ['==', /s1,s2-> str(s1) == str(s2)],
- ['!==',/s1,s2-> str(s1) != str(s2)],
- ['=', /f1,f2-> real(f1) == real(f2)],
- ['!=', /f1,f2-> real(f1) != real(f2)],
- ['>=', /f1,f2-> real(f1) >= real(f2)],
- ['<=', /f1,f2-> real(f1) <= real(f2)],
- ['>', /f1,f2-> real(f1) > real(f2)],
- ['<', /f1,f2-> real(f1) < real(f2)],
- ]
- self._rules = {}
- end
-end
-###
-
-tasmota._eqstr=/s1,s2-> str(s1) == str(s2)
-tasmota._neqstr=/s1,s2-> str(s1) != str(s2)
-tasmota._eq=/f1,f2-> real(f1) == real(f2)
-tasmota._neq=/f1,f2-> real(f1) != real(f2)
-tasmota._gt=/f1,f2-> real(f1) > real(f2)
-tasmota._lt=/f1,f2-> real(f1) < real(f2)
-tasmota._ge=/f1,f2-> real(f1) >= real(f2)
-tasmota._le=/f1,f2-> real(f1) <= real(f2)
-tasmota._op=[
- ['==',tasmota._eqstr],
- ['!==',tasmota._neqstr],
- ['=',tasmota._eq],
- ['!=',tasmota._neq],
- ['>=',tasmota._ge],
- ['<=',tasmota._le],
- ['>',tasmota._gt],
- ['<',tasmota._lt],
-]
-tasmota._operators="=<>!|"
-
-# split the item when there is an operator, returns a list of (left,op,right)
-# ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
-tasmota.find_op = def (item)
- var pos = charsinstring(item, tasmota._operators)
- if pos>=0
- var op_split = string.split(item,pos)
- #print(op_split)
- var op_left = op_split[0]
- var op_rest = op_split[1]
- # iterate through operators
- for op:tasmota._op
- if string.find(op_rest,op[0]) == 0
- var op_func = op[1]
- var op_right = string.split(op_rest,size(op[0]))[1]
- return [op_left,op_func,op_right]
- end
- end
- end
- return [item, nil, nil]
-end
-
-
-def findkeyi(m,keyi)
- var keyu = string.toupper(keyi)
- if classof(m) == map
- for k:m.keys()
- if string.toupper(k)==keyu || keyi=='?'
- return k
- end
- end
- end
-end
-
-
-tasmota.try_rule = def (ev, rule, f)
- var rl_list = tasmota.find_op(rule)
- var e=ev
- var rl=string.split(rl_list[0],'#')
- for it:rl
- found=findkeyi(e,it)
- if found == nil
- return false
- end
- e=e[found]
- end
- # check if condition is true
- if rl_list[1]
- # did we find a function
- if !rl_list[1](e,rl_list[2])
- # condition is not met
- return false
- end
- end
- f(e,ev)
- return true
-end
-tasmota_rules={}
-tasmota.rule = def(pat,f) tasmota_rules[pat] = f end
-
-tasmota.exec_rules = def (ev_json)
- var ev = json.load(ev_json)
- var ret = false
- if ev == nil
- log('BRY: ERROR, bad json: '+ev_json, 3)
- end
- for r:tasmota_rules.keys()
- ret = tasmota.try_rule(ev,r,tasmota_rules[r]) || ret
- end
- return ret
-end
-
-tasmota.delay = def(ms)
- tend = tasmota.millis(ms)
- while !tasmota.timereached(tend)
- tasmota.yield()
- end
-end
-
-def load(f)
- try
- if f[0] != '/' f = '/' + f end
- compile(f,'file')()
- except .. as e
- log(string.format("BRY: could not load file '%s' - %s",f,e))
- end
-end
-
-#- Test
-#################################################################
-
-def log(m) print(m) end
-def my_rule(e,ev) log("e1="+str(e)+" e2="+str(ev)) end
-
-tasmota.rule("ZBRECEIVED#?#LINKQUALITY", my_rule)
-tasmota.rule("ZBRECEIVED#0x1234", my_rule)
-
-tasmota.rule("ZBRECEIVED#?#LINKQUALITY<10", my_rule)
-
-tasmota.rule("Dimmer>50", my_rule)
-tasmota.rule("Dimmer=01", my_rule)
-
-
-tasmota.rule("Color==022600", my_rule)
-
-tasmota.exec_rules('{"Color":"022600"}')
-
-tasmota.exec_rules('{"ZbReceived":{"0x1234":{"Device":"0x1234","LinkQuality":50}}}')
-
-tasmota.exec_rules('{"Dimmer":10}')
-
-
-
-# tasmota.rule("DIMMER", my_rule)
-# tasmota.rule("DIMMER#DATA#DATA", my_rule)
-# tasmota.exec_rules('{"Dimmer":{"Data":50}}')
-
-
--#
-
-#-
-tasmota.find_op("aaa")
-tasmota.find_op("aaa>50")
--#
-
-#-
-# Example of backlog equivalent
-
-def backlog(cmd_list)
- delay_backlog = tasmota.getoption(34) # in milliseconds
- delay = 0
- for cmd:cmd_list
- tasmota.timer(delay, /-> tasmota.cmd(cmd))
- delay = delay + delay_backlog
- end
-end
-
-
-br def backlog(cmd_list) delay_backlog = tasmota.getoption(34) delay = 0 for cmd:cmd_list tasmota.timer(delay, /-> tasmota.cmd(cmd)) delay = delay + delay_backlog end end
-
-br backlog( [ "Power 0", "Status 4", "Power 1" ] )
-
--#
-
-#-
-
-tasmota.delay = def(ms) tend = tasmota.millis(ms) log(str(tasmota.millis())) while !tasmota.timereached(tend) end log(str(tasmota.millis())) end
-tasmota.delay = def(ms) a=0 tend = tasmota.millis(ms) log(str(tasmota.millis())) while !tasmota.timereached(tend) a=a+1 end log(str(tasmota.millis())) log(str(a)) end
-
--#
\ No newline at end of file
diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h
index 51c3999ac..f27f920d5 100644
--- a/tasmota/my_user_config.h
+++ b/tasmota/my_user_config.h
@@ -463,6 +463,11 @@
// #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code)
+// -- Berry Scripting Language - ESP32 only ----------------------------
+// #define USE_BERRY // Enable Berry scripting language
+ #define USE_BERRY_PSRAM // Allocate Berry memory in PSRAM if PSRAM is connected - this might be slightly slower but leaves main memory intact
+
+
// -- Optional modules ----------------------------
#define ROTARY_V1 // Add support for Rotary Encoder as used in MI Desk Lamp (+0k8 code)
#define ROTARY_MAX_STEPS 10 // Rotary step boundary
diff --git a/tasmota/support_esp.ino b/tasmota/support_esp.ino
index 1f929f400..65dd3871c 100644
--- a/tasmota/support_esp.ino
+++ b/tasmota/support_esp.ino
@@ -74,6 +74,10 @@ void *special_malloc(uint32_t size) {
return malloc(size);
}
+void *special_realloc(void *ptr, size_t size) {
+ return realloc(ptr, size);
+}
+
#endif
/*********************************************************************************************\
@@ -461,6 +465,13 @@ void *special_malloc(uint32_t size) {
return malloc(size);
}
}
+void *special_realloc(void *ptr, size_t size) {
+ if (psramFound()) {
+ return heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ } else {
+ return realloc(ptr, size);
+ }
+}
#endif // ESP32
diff --git a/tasmota/xdrv_52_0_berry_struct.ino b/tasmota/xdrv_52_0_berry_struct.ino
new file mode 100644
index 000000000..22f062ba5
--- /dev/null
+++ b/tasmota/xdrv_52_0_berry_struct.ino
@@ -0,0 +1,62 @@
+/*
+ xdrv_52_0_berry_struct.ino - Berry scripting language, native fucnctions
+
+ Copyright (C) 2021 Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry
+
+ 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 .
+*/
+
+
+#ifdef USE_BERRY
+
+#include
+
+typedef LList_elt log_elt; // store the string after the header to avoid double allocation if we had used char*
+
+class BerryLog {
+public:
+ // typedef LList_elt log_elt; // store the string after the header to avoid double allocation if we had used char*
+ inline static size_t size(size_t chars) { return sizeof(log_elt) + chars; }
+ inline bool isEmpty(void) const { return log.isEmpty(); }
+ log_elt * addString(const char * s, const char * prefix = nullptr, const char * suffix = nullptr) {
+ if (suffix == nullptr) { suffix = ""; }
+ if (prefix == nullptr) { prefix = ""; }
+ if (s == nullptr) { s = ""; }
+ size_t s_len = strlen_P(s) + strlen_P(prefix) + strlen_P(suffix);
+ if (0 == s_len) { return nullptr; } // do nothing
+ log_elt * elt = (log_elt*) ::operator new(sizeof(log_elt) + s_len + 1); // use low-level new to specify the bytes size
+ snprintf_P((char*) &elt->val(), s_len+1, PSTR("%s%s%s"), prefix, s, suffix);
+ log.addToLast(elt);
+ return elt;
+ }
+ void reset(void) {
+ log.reset();
+ }
+ LList log;
+};
+
+class BerrySupport {
+public:
+ bvm *vm = nullptr; // berry vm
+ bool rules_busy = false; // are we already processing rules, avoid infinite loop
+ bool autoexec_done = false; // do we still need to load 'autoexec.be'
+ bool repl_active = false; // is REPL running (activates log recording)
+ // output log is stored as a LinkedList of buffers
+ // and active only when a REPL command is running
+ BerryLog log;
+};
+BerrySupport berry;
+
+
+#endif // USE_BERRY
diff --git a/tasmota/xdrv_52_3_berry_tasmota.ino b/tasmota/xdrv_52_3_berry_tasmota.ino
index f39f3cc1c..bd9e1a8cc 100644
--- a/tasmota/xdrv_52_3_berry_tasmota.ino
+++ b/tasmota/xdrv_52_3_berry_tasmota.ino
@@ -23,6 +23,8 @@
#include
#include
+const uint32_t BERRY_MAX_LOGS = 16; // max number of print output recorded when outside of REPL, used to avoid infinite grow of logs
+
/*********************************************************************************************\
* Native functions mapped to Berry functions
*
@@ -30,18 +32,18 @@
*
* import tasmota
*
- * tasmota.getfreeheap() -> int
+ * tasmota.get_free_heap() -> int
* tasmota.publish(topic:string, payload:string[, retain:bool]) -> nil
* tasmota.cmd(command:string) -> string
- * tasmota.getoption(index:int) -> int
+ * tasmota.get_option(index:int) -> int
* tasmota.millis([delay:int]) -> int
- * tasmota.timereached(timer:int) -> bool
+ * tasmota.time_reached(timer:int) -> bool
* tasmota.yield() -> nil
*
- * tasmota.getlight([index:int = 0]) -> map
- * tasmota.getpower([index:int = 0]) -> bool
- * tasmota.setpower(idx:int, power:bool) -> bool or nil
- * tasmota.setlight(idx:int, values:map) -> map
+ * tasmota.get_light([index:int = 0]) -> map
+ * tasmota.get_power([index:int = 0]) -> bool
+ * tasmota.set_power(idx:int, power:bool) -> bool or nil
+ * tasmota.set_light(idx:int, values:map) -> map
*
\*********************************************************************************************/
extern "C" {
@@ -97,7 +99,7 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
- // Berry: tasmota.getoption(index:int) -> int
+ // Berry: tasmota.get_option(index:int) -> int
//
int32_t l_getoption(struct bvm *vm);
int32_t l_getoption(struct bvm *vm) {
@@ -110,7 +112,7 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
- // Berry: tasmota.timereached(timer:int) -> bool
+ // Berry: tasmota.time_reached(timer:int) -> bool
//
int32_t l_timereached(struct bvm *vm);
int32_t l_timereached(struct bvm *vm) {
@@ -145,7 +147,7 @@ extern "C" {
be_return_nil(vm);
}
- // Berry: tasmota.scaleuint(int * 5) -> int
+ // Berry: tasmota.scale_uint(int * 5) -> int
//
int32_t l_scaleuint(struct bvm *vm);
int32_t l_scaleuint(struct bvm *vm) {
@@ -470,6 +472,24 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
+#ifdef USE_I2C
+ // I2C specific
+ // Berry: `i2c_enabled(index:int) -> bool` is I2C device enabled
+ int32_t l_i2cenabled(struct bvm *vm);
+ int32_t l_i2cenabled(struct bvm *vm) {
+ int32_t top = be_top(vm); // Get the number of arguments
+ if (top == 2 && be_isint(vm, 2)) {
+ int32_t index = be_toint(vm, 2);
+ bool enabled = I2cEnabled(index);
+ be_pushbool(vm, enabled);
+ be_return(vm); // Return
+ }
+ be_raise(vm, kTypeError, nullptr);
+ }
+#else // USE_I2C
+ int32_t l_i2cenabled(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
+#endif // USE_I2C
+
}
/*********************************************************************************************\
@@ -522,6 +542,13 @@ extern "C" {
// called as a replacement to Berry `print()`
void berry_log(const char * berry_buf);
void berry_log(const char * berry_buf) {
+ if (berry.repl_active) {
+ if (berry.log.log.length() >= BERRY_MAX_LOGS) {
+ berry.log.log.remove(berry.log.log.head());
+ }
+ }
+ // AddLog(LOG_LEVEL_INFO, PSTR("[Add to log] %s"), berry_buf);
+ berry.log.addString(berry_buf, nullptr, "\n");
AddLog(LOG_LEVEL_INFO, PSTR("%s"), berry_buf);
}
diff --git a/tasmota/xdrv_52_3_berry_wire.ino b/tasmota/xdrv_52_3_berry_wire.ino
index 5959b98b9..70e457ca9 100644
--- a/tasmota/xdrv_52_3_berry_wire.ino
+++ b/tasmota/xdrv_52_3_berry_wire.ino
@@ -48,7 +48,7 @@ int32_t getBus(bvm *vm) {
*
* import wire
*
- * wire.getfreeheap() -> int
+ * wire.get_free_heap() -> int
*
\*********************************************************************************************/
extern "C" {
@@ -135,6 +135,8 @@ extern "C" {
int32_t b_wire_write(struct bvm *vm);
int32_t b_wire_write(struct bvm *vm) {
int32_t top = be_top(vm); // Get the number of arguments
+ const void * buf;
+ size_t len;
TwoWire & myWire = getWire(vm);
if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2))) {
if (be_isint(vm, 2)) {
@@ -143,6 +145,8 @@ extern "C" {
} else if (be_isstring(vm, 2)) {
const char * s = be_tostring(vm, 1);
myWire.write(s);
+ } else if ((buf = be_tobytes(vm, 2, &len)) != nullptr) {
+ myWire.write((uint8_t*) buf, len);
} else {
be_return_nil(vm);
}
@@ -211,7 +215,7 @@ extern "C" {
uint8_t addr = be_toint(vm, 2);
uint8_t reg = be_toint(vm, 3);
uint8_t size = be_toint(vm, 4);
- bool ok = I2cValidRead(addr, reg, size); // TODO
+ bool ok = I2cValidRead(addr, reg, size, bus); // TODO
if (ok) {
be_pushint(vm, i2c_buffer);
} else {
@@ -221,6 +225,62 @@ extern "C" {
}
be_raise(vm, kTypeError, nullptr);
}
+
+ // Berry: `read_bytes(address:int, reg:int, size:int) -> bytes() or nil`
+ int32_t b_wire_readbytes(struct bvm *vm);
+ int32_t b_wire_readbytes(struct bvm *vm) {
+ // int32_t top = be_top(vm); // Get the number of arguments
+ // int32_t bus = getBus(vm);
+ // if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
+ // uint8_t addr = be_toint(vm, 2);
+ // uint8_t reg = be_toint(vm, 3);
+ // uint8_t size = be_toint(vm, 4);
+ // bool ok = I2cValidRead(addr, reg, size, bus); // TODO
+ // if (ok) {
+ // be_pushint(vm, i2c_buffer);
+ // } else {
+ // be_pushnil(vm);
+ // }
+ // be_return(vm); // Return
+ // }
+ be_raise(vm, kTypeError, nullptr);
+ }
+
+ // Berry: `write_bytes(address:int, reg:int, val:bytes()) -> bool or nil`
+ int32_t b_wire_writebytes(struct bvm *vm);
+ int32_t b_wire_writebytes(struct bvm *vm) {
+ // int32_t top = be_top(vm); // Get the number of arguments
+ // int32_t bus = getBus(vm);
+ // if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
+ // uint8_t addr = be_toint(vm, 2);
+ // uint8_t reg = be_toint(vm, 3);
+ // uint8_t size = be_toint(vm, 4);
+ // bool ok = I2cValidRead(addr, reg, size, bus); // TODO
+ // if (ok) {
+ // be_pushint(vm, i2c_buffer);
+ // } else {
+ // be_pushnil(vm);
+ // }
+ // be_return(vm); // Return
+ // }
+ be_raise(vm, kTypeError, nullptr);
+ }
+
+ // Berry: `find(address:int) -> bool` true if device responds
+ int32_t b_wire_detect(struct bvm *vm);
+ int32_t b_wire_detect(struct bvm *vm) {
+ int32_t top = be_top(vm); // Get the number of arguments
+ TwoWire & myWire = getWire(vm);
+ if (top == 2 && be_isint(vm, 2)) {
+ uint8_t addr = be_toint(vm, 2);
+ // check the presence of the device
+ myWire.beginTransmission((uint8_t)addr);
+ bool found = (0 == myWire.endTransmission());
+ be_pushbool(vm, found);
+ be_return(vm); // Return
+ }
+ be_raise(vm, kTypeError, nullptr);
+ }
#else // USE_I2C
//
int32_t b_wire_i2cmissing(struct bvm *vm);
@@ -239,6 +299,9 @@ extern "C" {
int32_t b_wire_scan(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
int32_t b_wire_validwrite(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
int32_t b_wire_validread(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
+ int32_t b_wire_readbytes(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
+ int32_t b_wire_writebytes(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
+ int32_t b_wire_detect(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
#endif // USE_I2C
}
diff --git a/tasmota/xdrv_52_7_berry_embedded.ino b/tasmota/xdrv_52_7_berry_embedded.ino
index 83a3895e7..65dfb616f 100644
--- a/tasmota/xdrv_52_7_berry_embedded.ino
+++ b/tasmota/xdrv_52_7_berry_embedded.ino
@@ -55,14 +55,11 @@ const char berry_prog[] =
"/f1,f2-> real(f1) < real(f2),"
"] "
"self._operators = \"=<>!|\" "
- "self._rules = {} "
- "self._timers = [] "
- "self._cmd = {} "
"end "
- // add `charsinstring(s:string,c:string) -> int``
+ // add `chars_in_string(s:string,c:string) -> int``
// looks for any char in c, and return the position of the first chat
// or -1 if not found
- "def charsinstring(s,c) "
+ "def chars_in_string(s,c) "
"for i:0..size(s)-1 "
"for j:0..size(c)-1 "
"if s[i] == c[j] return i end "
@@ -72,7 +69,7 @@ const char berry_prog[] =
"end "
// find a key in map, case insensitive, return actual key or nil if not found
- "def findkeyi(m,keyi) "
+ "def find_key_i(m,keyi) "
"import string "
"var keyu = string.toupper(keyi) "
"if classof(m) == map "
@@ -84,14 +81,11 @@ const char berry_prog[] =
"end "
"end "
- // Rules
- "def addrule(pat,f) self._rules[pat] = f end "
-
// # split the item when there is an operator, returns a list of (left,op,right)
// # ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
"def find_op(item) "
"import string "
- "var pos = self.charsinstring(item, self._operators) "
+ "var pos = self.chars_in_string(item, self._operators) "
"if pos>=0 "
"var op_split = string.split(item,pos) "
// #print(op_split)
@@ -109,6 +103,14 @@ const char berry_prog[] =
"end "
"return [item, nil, nil] "
"end "
+
+ // Rules
+ "def add_rule(pat,f) "
+ "if !self._rules "
+ "self._rules={} "
+ "end "
+ "self._rules[pat] = f "
+ "end "
// Rules trigger if match. return true if match, false if not
"def try_rule(ev, rule, f) "
@@ -117,7 +119,7 @@ const char berry_prog[] =
"var e=ev "
"var rl=string.split(rl_list[0],'#') "
"for it:rl "
- "found=self.findkeyi(e,it) "
+ "found=self.find_key_i(e,it) "
"if found == nil "
"return false "
"end "
@@ -138,55 +140,69 @@ const char berry_prog[] =
// Run rules, i.e. check each individual rule
// Returns true if at least one rule matched, false if none
"def exec_rules(ev_json) "
- "import json "
- "var ev = json.load(ev_json) "
- "var ret = false "
- "if ev == nil "
- "print('BRY: ERROR, bad json: '+ev_json, 3) "
- "else "
- "for r: self._rules.keys() "
- "ret = self.try_rule(ev,r,self._rules[r]) || ret "
+ "if self._rules "
+ "import json "
+ "var ev = json.load(ev_json) "
+ "var ret = false "
+ "if ev == nil "
+ "print('BRY: ERROR, bad json: '+ev_json, 3) "
+ "else "
+ "for r: self._rules.keys() "
+ "ret = self.try_rule(ev,r,self._rules[r]) || ret "
+ "end "
"end "
+ "return ret "
"end "
- "return ret "
+ "return false "
"end "
- "def settimer(delay,f) self._timers.push([self.millis(delay),f]) end "
+ "def set_timer(delay,f) "
+ "if !self._timers self._timers=[] end "
+ "self._timers.push([self.millis(delay),f]) "
+ "end "
+ // run every 50ms tick
"def run_deferred() "
- "var i=0 "
- "while i
+#define BERRY_CONSOLE_CMD_DELIMITER "\x01"
+
const char kBrCommands[] PROGMEM = D_PRFX_BR "|" // prefix
D_CMND_BR_RUN "|" D_CMND_BR_RESET
;
@@ -32,14 +34,6 @@ void (* const BerryCommand[])(void) PROGMEM = {
CmndBrRun, CmndBrReset,
};
-class BerrySupport {
-public:
- bvm *vm = nullptr; // berry vm
- bool rules_busy = false; // are we already processing rules, avoid infinite loop
- bool autoexec_done = false; // do we still need to load 'autoexec.be'
-};
-BerrySupport berry;
-
//
// Sanity Check for be_top()
//
@@ -64,79 +58,13 @@ bool callBerryRule(void) {
berry.rules_busy = true;
char * json_event = TasmotaGlobal.mqtt_data;
bool serviced = false;
-
- checkBeTop();
- be_getglobal(berry.vm, "_exec_rules");
- if (!be_isnil(berry.vm, -1)) {
-
- // {
- // String event_saved = TasmotaGlobal.mqtt_data;
- // // json_event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
- // // json_event = {"System":{"Boot":1}}
- // // json_event = {"SerialReceived":"on"} - invalid but will be expanded to {"SerialReceived":{"Data":"on"}}
- // char *p = strchr(json_event, ':');
- // if ((p != NULL) && !(strchr(++p, ':'))) { // Find second colon
- // event_saved.replace(F(":"), F(":{\"Data\":"));
- // event_saved += F("}");
- // // event_saved = {"SerialReceived":{"Data":"on"}}
- // }
- // be_pushstring(berry.vm, event_saved.c_str());
- // }
- be_pushstring(berry.vm, TasmotaGlobal.mqtt_data);
- int ret = be_pcall(berry.vm, 1);
- serviced = be_tobool(berry.vm, 1);
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Event (%s) serviced=%d"), TasmotaGlobal.mqtt_data, serviced);
- be_pop(berry.vm, 2); // remove function object
- } else {
- be_pop(berry.vm, 1); // remove nil object
- }
- checkBeTop();
+ serviced = callBerryEventDispatcher(PSTR("exec_rules"), nullptr, 0, TasmotaGlobal.mqtt_data);
berry.rules_busy = false;
-
- return serviced; // TODO event not handled
-}
-
-bool callBerryCommand(void) {
- bool serviced = false;
-
- checkBeTop();
- be_getglobal(berry.vm, "_exec_cmd");
- if (!be_isnil(berry.vm, -1)) {
- be_pushstring(berry.vm, XdrvMailbox.topic);
- be_pushint(berry.vm, XdrvMailbox.index);
- be_pushstring(berry.vm, XdrvMailbox.data);
- int ret = be_pcall(berry.vm, 3);
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: top=%d", be_top(berry.vm));
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(1)=%s", be_typename(berry.vm, 1));
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(2)=%s", be_typename(berry.vm, 2));
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(3)=%s", be_typename(berry.vm, 3));
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(4)=%s", be_typename(berry.vm, 4));
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(5)=%s", be_typename(berry.vm, 5));
- serviced = be_tobool(berry.vm, 1); // return value is in slot 1
- // AddLog(LOG_LEVEL_INFO, "callBerryCommand: serviced=%d", serviced);
- be_pop(berry.vm, 4); // remove function object
- } else {
- be_pop(berry.vm, 1); // remove nil object
- }
- checkBeTop();
-
return serviced; // TODO event not handled
}
size_t callBerryGC(void) {
- size_t ram_used = 0;
- checkBeTop();
- be_getglobal(berry.vm, "_gc");
- if (!be_isnil(berry.vm, -1)) {
- int ret = be_pcall(berry.vm, 0);
- ram_used = be_toint(berry.vm, 1);
- be_pop(berry.vm, 1); // remove function object
- } else {
- be_pop(berry.vm, 1); // remove nil object
- }
- checkBeTop();
-
- return ram_used;
+ return callBerryEventDispatcher(PSTR("gc"), nullptr, 0, nullptr);
}
// void callBerryMqttData(void) {
@@ -161,16 +89,79 @@ size_t callBerryGC(void) {
// checkBeTop();
// }
-// call a function (if exists) of type void -> void
-void callBerryFunctionVoid(const char * fname) {
- if (nullptr == berry.vm) { return; }
- checkBeTop();
- be_getglobal(berry.vm, fname);
+/*
+// Call a method of a global object, with n args
+// Before: stack must containt n args
+// After: stack contains return value or nil if something wrong (args removes)
+// returns true is successful, false if object or method not found
+bool callMethodObjectWithArgs(const char * objname, const char * method, size_t argc) {
+ if (nullptr == berry.vm) { return false; }
+ int32_t top = be_top(berry.vm);
+ // stacks contains n x arg
+ be_getglobal(berry.vm, objname);
+ // stacks contains n x arg + object
if (!be_isnil(berry.vm, -1)) {
- be_pcall(berry.vm, 0);
+ be_getmethod(berry.vm, -1, method);
+ // stacks contains n x arg + object + method
+ if (!be_isnil(berry.vm, -1)) {
+ // reshuffle the entire stack since we want: method + object + n x arg
+ be_pushvalue(berry.vm, -1); // add instance as first arg
+ // stacks contains n x arg + object + method + method
+ be_pushvalue(berry.vm, -3); // add instance as first arg
+ // stacks contains n x arg + object + method + method + object
+ // now move args 2 slots up to make room for method and object
+ for (uint32_t i = 1; i <= argc; i++) {
+ be_moveto(berry.vm, -4 - i, -2 - i);
+ }
+ // stacks contains free + free + n x arg + method + object
+ be_moveto(berry.vm, -2, -4 - argc);
+ be_moveto(berry.vm, -1, -3 - argc);
+ // stacks contains method + object + n x arg + method + object
+ be_pop(berry.vm, 2);
+ // stacks contains method + object + n x arg
+ be_pcall(berry.vm, argc + 1);
+ // stacks contains return_val + object + n x arg
+ be_pop(berry.vm, argc + 1);
+ // stacks contains return_val
+ return true;
+ }
+ be_pop(berry.vm, 1); // remove method
+ // stacks contains n x arg + object
}
- be_pop(berry.vm, 1); // remove function or nil object
+ // stacks contains n x arg + object
+ be_pop(berry.vm, argc + 1); // clear stack
+ be_pushnil(berry.vm); // put nil object
+ return false;
+}
+*/
+
+
+// call the event dispatcher from Tasmota object
+int32_t callBerryEventDispatcher(const char *type, const char *cmd, int32_t idx, const char *payload) {
+ int32_t ret = 0;
+
+ if (nullptr == berry.vm) { return ret; }
checkBeTop();
+ be_getglobal(berry.vm, PSTR("tasmota"));
+ if (!be_isnil(berry.vm, -1)) {
+ be_getmethod(berry.vm, -1, PSTR("event"));
+ if (!be_isnil(berry.vm, -1)) {
+ be_pushvalue(berry.vm, -2); // add instance as first arg
+ be_pushstring(berry.vm, type != nullptr ? type : "");
+ be_pushstring(berry.vm, cmd != nullptr ? cmd : "");
+ be_pushint(berry.vm, idx);
+ be_pushstring(berry.vm, payload != nullptr ? payload : "{}"); // empty json
+ be_pcall(berry.vm, 5); // 5 arguments
+ be_pop(berry.vm, 5);
+ if (be_isint(berry.vm, -1)) {
+ ret = be_toint(berry.vm, -1);
+ }
+ }
+ be_pop(berry.vm, 1); // remove method
+ }
+ be_pop(berry.vm, 1); // remove instance object
+ checkBeTop();
+ return ret;
}
/*********************************************************************************************\
@@ -349,6 +340,313 @@ void CmndBrReset(void) {
BrReset();
}
+/*********************************************************************************************\
+ * Berry console
+\*********************************************************************************************/
+#ifdef USE_WEBSERVER
+
+void BrREPLRun(char * cmd) {
+ if (berry.vm == nullptr) { return; }
+
+ size_t cmd_len = strlen(cmd);
+ size_t cmd2_len = cmd_len + 12;
+ char * cmd2 = (char*) malloc(cmd2_len);
+ do {
+ int32_t ret_code;
+
+ snprintf_P(cmd2, cmd2_len, PSTR("return (%s)"), cmd);
+ ret_code = be_loadbuffer(berry.vm, PSTR("input"), cmd2, strlen(cmd2));
+ // AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_loadbuffer cmd2 '%s', ret=%i"), cmd2, ret_code);
+ if (be_getexcept(berry.vm, ret_code) == BE_SYNTAX_ERROR) {
+ be_pop(berry.vm, 2); // remove exception values
+ // if fails, try the direct command
+ ret_code = be_loadbuffer(berry.vm, PSTR("input"), cmd, cmd_len);
+ // AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_loadbuffer cmd1 '%s', ret=%i"), cmd, ret_code);
+ }
+ if (0 == ret_code) { // code is ready to run
+ ret_code = be_pcall(berry.vm, 0); // execute code
+ // AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_pcall ret=%i"), ret_code);
+ if (0 == ret_code) {
+ if (!be_isnil(berry.vm, 1)) {
+ const char * ret_val = be_tostring(berry.vm, 1);
+ berry.log.addString(ret_val, nullptr, "\n");
+ // AddLog_P(LOG_LEVEL_INFO, PSTR(">>> %s"), ret_val);
+ }
+ be_pop(berry.vm, 1);
+ }
+ }
+ if (BE_EXCEPTION == ret_code) {
+ be_dumpstack(berry.vm);
+ char exception_s[120];
+ ext_snprintf_P(exception_s, sizeof(exception_s), PSTR("%s: %s"), be_tostring(berry.vm, -2), be_tostring(berry.vm, -1));
+ berry.log.addString(exception_s, nullptr, "\n");
+ // AddLog_P(LOG_LEVEL_INFO, PSTR(">>> %s"), exception_s);
+ be_pop(berry.vm, 2);
+ }
+ } while(0);
+
+ if (cmd2 != nullptr) {
+ free(cmd2);
+ cmd2 = nullptr;
+ }
+ checkBeTop();
+}
+
+const char HTTP_SCRIPT_BERRY_CONSOLE[] PROGMEM =
+ "var sn=0,id=0,ft,ltm=%d;" // Scroll position, Get most of weblog initially
+ // Console command history
+ "var hc=[],cn=0;" // hc = History commands, cn = Number of history being shown
+
+ "function l(p){" // Console log and command service
+ "var c,cc,o='';"
+ "clearTimeout(lt);"
+ "clearTimeout(ft);"
+ "t=eb('t1');"
+ "if(p==1){"
+ "c=eb('c1');" // Console command id
+ "cc=c.value.trim();"
+ "if(cc){"
+ "o='&c1='+encodeURIComponent(cc);"
+ "hc.length>19&&hc.pop();"
+ "hc.unshift(cc);"
+ "cn=0;"
+ "}"
+ "c.value='';"
+ "t.scrollTop=99999;"
+ "sn=t.scrollTop;"
+ "}"
+ "if(t.scrollTop>=sn){" // User scrolled back so no updates
+ "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1)
+ "x=new XMLHttpRequest();"
+ "x.onreadystatechange=function(){"
+ "if(x.readyState==4&&x.status==200){"
+ "var d,t1;"
+ "d=x.responseText.split(/" BERRY_CONSOLE_CMD_DELIMITER "/);" // Field separator
+ "var d1=d.shift();"
+ "if(d1){"
+ "t1=document.createElement('div');"
+ "t1.classList.add('br1');"
+ "t1.innerText=d1;"
+ "t.appendChild(t1);"
+ "}"
+ "d1=d.shift();"
+ "if(d1){"
+ "t1=document.createElement('div');"
+ "t1.classList.add('br2');"
+ "t1.innerText=d1;"
+ "t.appendChild(t1);"
+ "}"
+ "t.scrollTop=99999;"
+ "sn=t.scrollTop;"
+ "clearTimeout(ft);"
+ "lt=setTimeout(l,ltm);" // webrefresh timer....
+ "}"
+ "};"
+ "x.open('GET','bs?c2='+id+o,true);" // Related to Webserver->hasArg("c2") and WebGetArg("c2", stmp, sizeof(stmp))
+ "x.send();"
+ "ft=setTimeout(l,20000);" // fail timeout, triggered 20s after asking for XHR
+ "}else{"
+ "lt=setTimeout(l,ltm);" // webrefresh timer....
+ "}"
+ "c1.focus();"
+ "return false;"
+ "}"
+ "wl(l);" // Load initial console text
+; // Add console command key eventlistener after name has been synced with id (= wl(jd))
+
+const char HTTP_SCRIPT_BERRY_CONSOLE2[] PROGMEM =
+ // // Console command history
+ // "var hc=[],cn=0;" // hc = History commands, cn = Number of history being shown
+ "var pc=0;" // pc = previous char
+ "function h(){"
+// "if(!(navigator.maxTouchPoints||'ontouchstart'in document.documentElement)){eb('c1').autocomplete='off';}" // No touch so stop browser autocomplete
+ "eb('c1').addEventListener('keydown',function(e){"
+ "var b=eb('c1'),c=e.keyCode;" // c1 = Console command id
+ "if((38==c||40==c)&&0==this.selectionStart&&0==this.selectionEnd){"
+ "b.autocomplete='off';"
+ "e.preventDefault();"
+ "38==c?(++cn>hc.length&&(cn=hc.length),b.value=hc[cn-1]||''):" // ArrowUp
+ "40==c?(0>--cn&&(cn=0),b.value=hc[cn-1]||''):" // ArrowDown
+ "0;"
+ "this.selectionStart=this.selectionEnd=0;"
+ "}" // ArrowUp or ArrowDown must be a keyboard so stop browser autocomplete
+ "if(c==13&&pc==13){"
+ "e.preventDefault();" // prevent 'enter' from being inserted
+ "l(1);"
+ "}"
+ "if(c==9){"
+ "e.preventDefault();"
+ "var start=this.selectionStart;"
+ "var end=this.selectionEnd;"
+ // set textarea value to: text before caret + tab + text after caret
+ "this.value=this.value.substring(0, start)+\" \"+this.value.substring(end);"
+ // put caret at right position again
+ "this.selectionStart=this.selectionEnd=start + 1;"
+ "}"
+ "pc=c;" // record previous key
+ // "13==c&&(hc.length>19&&hc.pop(),hc.unshift(b.value),cn=0)" // Enter, 19 = Max number -1 of commands in history
+ "});"
+ "}"
+ "wl(h);"; // Add console command key eventlistener after name has been synced with id (= wl(jd))
+
+const char HTTP_BERRY_STYLE_CMND[] PROGMEM =
+ ""
+ ;
+
+const char HTTP_BERRY_FORM_CMND[] PROGMEM =
+ "
"
+ ""
+ "
Welcome to the Berry Scripting console. "
+ "Check the
documentation."
+ "
"
+ "
"
+ // ""
+ // "
"
+ "";
+
+const char HTTP_BTN_BERRY_CONSOLE[] PROGMEM =
+ "
";
+
+
+void HandleBerryConsoleRefresh(void)
+{
+ String svalue = Webserver->arg(F("c1"));
+
+ svalue.trim();
+ if (svalue.length()) {
+ berry.log.reset(); // clear all previous logs
+ berry.repl_active = true; // start recording
+ AddLog_P(LOG_LEVEL_INFO, PSTR("BRY: received command %s"), svalue.c_str());
+ berry.log.addString(svalue.c_str(), nullptr, BERRY_CONSOLE_CMD_DELIMITER);
+
+ // Call berry
+ BrREPLRun((char*)svalue.c_str());
+ berry.repl_active = false; // don't record further
+ }
+
+ WSContentBegin(200, CT_PLAIN);
+
+ if (!berry.log.isEmpty()) {
+
+ WSContentFlush();
+
+ for (auto & l: berry.log.log) {
+ _WSContentSend((char*) l);
+ }
+
+ berry.log.reset();
+ }
+ WSContentEnd();
+}
+
+void HandleBerryConsole(void)
+{
+ if (!HttpCheckPriviledgedAccess()) { return; }
+ // int i=16;
+ // // AddLog(LOG_LEVEL_INFO, PSTR("Size = %d %d"), sizeof(LList_elt), sizeof(LList_elt)+12);
+ // LList_elt * elt = (LList_elt*) ::operator new(sizeof(LList_elt) + 12);
+
+ if (Webserver->hasArg(F("c2"))) { // Console refresh requested
+ HandleBerryConsoleRefresh();
+ return;
+ }
+
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Berry " D_CONSOLE));
+
+ WSContentStart_P(PSTR("Berry " D_CONSOLE));
+ WSContentSend_P(HTTP_SCRIPT_BERRY_CONSOLE, Settings.web_refresh);
+ WSContentSend_P(HTTP_SCRIPT_BERRY_CONSOLE2);
+ WSContentSendStyle();
+ WSContentFlush();
+ _WSContentSend(HTTP_BERRY_STYLE_CMND);
+ _WSContentSend(HTTP_BERRY_FORM_CMND);
+ WSContentSpaceButton(BUTTON_MAIN);
+ WSContentStop();
+}
+
+// void HandleBerryConsoleRefresh(void)
+// {
+// String svalue = Webserver->arg(F("c1"));
+// if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
+// // TODO run command and store result
+// // AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str());
+// // ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE);
+// }
+
+// char stmp[8];
+// WebGetArg(PSTR("c2"), stmp, sizeof(stmp));
+// uint32_t index = 0; // Initial start, dump all
+// if (strlen(stmp)) { index = atoi(stmp); }
+
+// WSContentBegin(200, CT_PLAIN);
+// WSContentSend_P(PSTR("%d}1%d}1"), TasmotaGlobal.log_buffer_pointer, Web.reset_web_log_flag);
+// if (!Web.reset_web_log_flag) {
+// index = 0;
+// Web.reset_web_log_flag = true;
+// }
+// bool cflg = (index);
+// char* line;
+// size_t len;
+// while (GetLog(Settings.weblog_level, &index, &line, &len)) {
+// if (len > sizeof(TasmotaGlobal.mqtt_data) -2) { len = sizeof(TasmotaGlobal.mqtt_data); }
+// char stemp[len +1];
+// strlcpy(stemp, line, len);
+// WSContentSend_P(PSTR("%s%s"), (cflg) ? PSTR("\n") : "", stemp);
+// cflg = true;
+// }
+// WSContentSend_P(PSTR("}1"));
+// WSContentEnd();
+// }
+#endif // USE_WEBSERVER
+
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
@@ -363,51 +661,63 @@ bool Xdrv52(uint8_t function)
break;
case FUNC_LOOP:
if (!berry.autoexec_done) {
- BrAutoexec();
+ BrAutoexec(); // run autoexec.be at first tick, so we know all modules are initialized
berry.autoexec_done = true;
}
break;
+
+ // Berry wide commands and events
+ case FUNC_RULES_PROCESS:
+ result = callBerryRule();
+ break;
+ case FUNC_MQTT_DATA:
+ result = callBerryEventDispatcher(PSTR("mqtt_data"), XdrvMailbox.topic, 0, XdrvMailbox.data);
+ break;
case FUNC_EVERY_50_MSECOND:
- callBerryFunctionVoid(PSTR("_run_deferred"));
- break;
- case FUNC_EVERY_100_MSECOND:
- callBerryFunctionVoid(PSTR("every_100ms"));
- break;
- case FUNC_EVERY_SECOND:
- callBerryFunctionVoid(PSTR("every_second"));
+ callBerryEventDispatcher(PSTR("every_50ms"), nullptr, 0, nullptr);
break;
case FUNC_COMMAND:
result = DecodeCommand(kBrCommands, BerryCommand);
if (!result) {
- result = callBerryCommand();
+ result = callBerryEventDispatcher(PSTR("cmd"), XdrvMailbox.topic, XdrvMailbox.index, XdrvMailbox.data);
}
break;
+
+ // Module specific events
+ case FUNC_EVERY_100_MSECOND:
+ callBerryEventDispatcher(PSTR("every_100ms"), nullptr, 0, nullptr);
+ break;
+ case FUNC_EVERY_SECOND:
+ callBerryEventDispatcher(PSTR("every_second"), nullptr, 0, nullptr);
+ break;
// case FUNC_SET_POWER:
// break;
- case FUNC_RULES_PROCESS:
- result = callBerryRule();
- break;
#ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON:
+ WSContentSend_P(HTTP_BTN_BERRY_CONSOLE);
+ callBerryEventDispatcher(PSTR("web_add_button"), nullptr, 0, nullptr);
break;
case FUNC_WEB_ADD_MAIN_BUTTON:
-
+ callBerryEventDispatcher(PSTR("web_add_main_button"), nullptr, 0, nullptr);
break;
case FUNC_WEB_ADD_HANDLER:
+ callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr);
+ WebServer_on(PSTR("/bs"), HandleBerryConsole);
break;
#endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART:
- break;
- case FUNC_MQTT_DATA:
- // callBerryMqttData();
+ callBerryEventDispatcher(PSTR("save_before_restart"), nullptr, 0, nullptr);
break;
case FUNC_WEB_SENSOR:
+ callBerryEventDispatcher(PSTR("web_sensor"), nullptr, 0, nullptr);
break;
case FUNC_JSON_APPEND:
+ callBerryEventDispatcher(PSTR("json_aooend"), nullptr, 0, nullptr);
break;
case FUNC_BUTTON_PRESSED:
+ callBerryEventDispatcher(PSTR("button_pressed"), nullptr, 0, nullptr);
break;
From 247ff59b78b1b419d870ada21037cb4e80c5363c Mon Sep 17 00:00:00 2001
From: Simon Ratcliffe
Date: Sun, 21 Mar 2021 12:00:33 +1100
Subject: [PATCH 12/35] Remove superfluous debug messages
---
tasmota/xsns_29_mcp230xx.ino | 2 --
1 file changed, 2 deletions(-)
diff --git a/tasmota/xsns_29_mcp230xx.ino b/tasmota/xsns_29_mcp230xx.ino
index e5cf3de09..32ec7c769 100644
--- a/tasmota/xsns_29_mcp230xx.ino
+++ b/tasmota/xsns_29_mcp230xx.ino
@@ -113,7 +113,6 @@ const char* ConvertNumTxt(uint8_t statu, uint8_t pinmod=0) {
#endif // USE_MCP230xx_OUTPUT
#ifdef USE_MCP230xx_OUTPUT
if ((6 == pinmod) && (statu < 2)) { statu = 1-statu; }
- AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: ConvertNumTxt config=%d save_state=%d"),config, Settings.flag.save_state);
if ((config) && (Settings.flag.save_state)) {
return "SAVED";
}
@@ -205,7 +204,6 @@ void MCP230xx_ApplySettings(void)
reg_portpins[mcp230xx_port] |= (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].saved_state << idx);
} else {
if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].keep_output) { // Read the value to use from the MCP230xx
- AddLog(LOG_LEVEL_DEBUG, PSTR("MCP: readpins=%d or_val=%d"),reg_readpins, reg_readpins & (1 << idx));
reg_portpins[mcp230xx_port] |= reg_readpins & (1 << idx);
}
else if (Settings.mcp230xx_config[idx+(mcp230xx_port*8)].pullup) {
From 8cb98dac184b3a17a4e437b4998dd529365593dc Mon Sep 17 00:00:00 2001
From: Pagliarulo Onofrio
Date: Sun, 21 Mar 2021 09:26:10 +0100
Subject: [PATCH 13/35] fix alexa discovery problems
---
.../tasmota/xdrv_20_hue_20210321091627.ino | 1096 ++++++++++++++++
.../tasmota/xdrv_20_hue_20210321092038.ino | 1097 +++++++++++++++++
.../tasmota/xdrv_20_hue_20210321092519.ino | 1097 +++++++++++++++++
tasmota/xdrv_20_hue.ino | 3 +-
4 files changed, 3292 insertions(+), 1 deletion(-)
create mode 100644 .history/tasmota/xdrv_20_hue_20210321091627.ino
create mode 100644 .history/tasmota/xdrv_20_hue_20210321092038.ino
create mode 100644 .history/tasmota/xdrv_20_hue_20210321092519.ino
diff --git a/.history/tasmota/xdrv_20_hue_20210321091627.ino b/.history/tasmota/xdrv_20_hue_20210321091627.ino
new file mode 100644
index 000000000..37c3308d0
--- /dev/null
+++ b/.history/tasmota/xdrv_20_hue_20210321091627.ino
@@ -0,0 +1,1096 @@
+/*
+ xdrv_20_hue.ino - Philips Hue support for Tasmota
+
+ Copyright (C) 2021 Heiko Krupp and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
+/*********************************************************************************************\
+ * Philips Hue bridge emulation
+ *
+ * Hue Bridge UPNP support routines
+ * Need to send 3 response packets with varying ST and USN
+ *
+ * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
+ * Philips Lighting is 00:17:88:00:00:00
+\*********************************************************************************************/
+
+#define XDRV_20 20
+
+#include "UnishoxStrings.h"
+
+const char HUE_RESP_MSG_U[] PROGMEM =
+ //=HUE_RESP_RESPONSE
+ "HTTP/1.1 200 OK\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n"
+ "LOCATION: http://%s:80/description.xml\r\n"
+ "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+ "hue-bridgeid: %s\r\n"
+ "\0"
+ //=HUE_RESP_ST1
+ "ST: upnp:rootdevice\r\n"
+ "USN: uuid:%s::upnp:rootdevice\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST2
+ "ST: uuid:%s\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST3
+ "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0";
+
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_RESP_RESPONSE=0,
+ HUE_RESP_ST1=185,
+ HUE_RESP_ST2=240,
+ HUE_RESP_ST3=270,
+};
+
+// Compressed from 328 to 247, -24.7%
+const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
+ "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
+ "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
+ "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
+ "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
+ "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
+ "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
+ "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
+ "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
+ "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
+ "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
+ "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
+ "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
+
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+// const char HUE_RESPONSE[] PROGMEM =
+// "HTTP/1.1 200 OK\r\n"
+// "HOST: 239.255.255.250:1900\r\n"
+// "CACHE-CONTROL: max-age=100\r\n"
+// "EXT:\r\n"
+// "LOCATION: http://%s:80/description.xml\r\n"
+// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+// "hue-bridgeid: %s\r\n";
+// const char HUE_ST1[] PROGMEM =
+// "ST: upnp:rootdevice\r\n"
+// "USN: uuid:%s::upnp:rootdevice\r\n"
+// "\r\n";
+// const char HUE_ST2[] PROGMEM =
+// "ST: uuid:%s\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+// const char HUE_ST3[] PROGMEM =
+// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+
+const char HUE_API_U[] PROGMEM =
+ //=HUE_INVALID
+ "/invalid/"
+ "\0"
+ //=HUE_ROOT
+ "/"
+ "\0"
+ //=HUE_CONFIG
+ "/config"
+ "\0"
+ //=HUE_LIGHTS_API
+ "/lights"
+ "\0"
+ //=HUE_GROUPS
+ "/groups"
+ "\0"
+ //=HUE_SCHEDULES
+ "/schedules"
+ "\0"
+ //=HUE_SENSORS
+ "/sensors"
+ "\0"
+ //=HUE_SCENES
+ "/scenes"
+ "\0"
+ //=HUE_RULES
+ "/rules"
+ "\0"
+ //=HUE_RESOURCELINKS
+ "/resourcelinks"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_INVALID=0,
+ HUE_ROOT=10,
+ HUE_CONFIG=12,
+ HUE_LIGHTS_API=20,
+ HUE_GROUPS=28,
+ HUE_SCHEDULES=36,
+ HUE_SENSORS=47,
+ HUE_SCENES=56,
+ HUE_RULES=64,
+ HUE_RESOURCELINKS=71,
+};
+
+// Compressed from 86 to 74, -14.0%
+const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
+ "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
+ "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
+ "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+String HueBridgeId(void)
+{
+ String temp = WiFi.macAddress();
+ temp.replace(":", "");
+ String bridgeid = temp.substring(0, 6);
+ bridgeid += F("FFFE");
+ bridgeid += temp.substring(6);
+ return bridgeid; // 5CCF7FFFFE139F3D
+}
+
+String HueSerialnumber(void)
+{
+ String serial = WiFi.macAddress();
+ serial.replace(":", "");
+ serial.toLowerCase();
+ return serial; // 5ccf7f139f3d
+}
+
+String HueUuid(void)
+{
+ String uuid = F("f6543a06-da50-11ba-8d8f-");
+ uuid += HueSerialnumber();
+ return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
+}
+
+void HueRespondToMSearch(void)
+{
+ char message[TOPSZ];
+
+ if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
+ UnishoxStrings msg(HUE_RESP_MSG);
+ char response[320];
+ snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
+ int len = strlen(response);
+ String uuid = HueUuid();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
+ } else {
+ snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
+ }
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
+ message, udp_remote_ip.toString().c_str(), udp_remote_port);
+}
+
+/*********************************************************************************************\
+ * Hue web server additions
+\*********************************************************************************************/
+
+//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
+//Successfully compressed from 625 to 391 bytes (-37.4%)
+const size_t HUE_DESCRIPTION_XML_SIZE = 625;
+const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
+ "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
+ "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
+ "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
+ "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
+ "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
+ "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
+ "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
+ "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
+ "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
+ "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
+ "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
+ "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
+ "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
+ "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
+ "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
+ "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
+ "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
+ "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
+ "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
+// const char HUE_DESCRIPTION_XML[] PROGMEM =
+// ""
+// ""
+// ""
+// "1"
+// "0"
+// ""
+// "http://{x1:80/"
+// ""
+// "urn:schemas-upnp-org:device:Basic:1"
+// "Amazon-Echo-HA-Bridge ({x1)"
+// "Royal Philips Electronics"
+// "http://www.philips.com"
+// "Philips hue Personal Wireless Lighting"
+// "Philips hue bridge 2012"
+// "929000226503"
+// "{x3"
+// "uuid:{x2"
+// ""
+// "\r\n"
+// "\r\n";
+
+const char HUE_LIGHTS_U[] PROGMEM =
+ //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
+ "%s\"alert\":\"none\","
+ "\"effect\":\"none\","
+ "\"reachable\":true}"
+ "\0"
+ //=HUE_LIGHTS_STATUS_JSON2
+ ",\"type\":\"Extended color light\","
+ "\"name\":\"%s\","
+ "\"modelid\":\"%s\","
+ "\"manufacturername\":\"%s\","
+ "\"uniqueid\":\"%s\"}"
+ "\0"
+ //=HUE_GROUP0_STATUS_JSON
+ "{\"name\":\"Group 0\","
+ "\"lights\":[{l1],"
+ "\"type\":\"LightGroup\","
+ "\"action\":"
+ "\0"
+ //=HUE_ERROR_JSON
+ "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
+ "\0"
+ //=HUE_RESP_ON
+ "{\"success\":{\"/lights/%d/state/on\":%s}}"
+ "\0"
+ //=HUE_RESP_NUM
+ "{\"success\":{\"/lights/%d/state/%s\":%d}}"
+ "\0"
+ //=HUE_RESP_XY
+ "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
+ HUE_LIGHTS_STATUS_JSON2=51,
+ HUE_GROUP0_STATUS_JSON=150,
+ HUE_ERROR_JSON=213,
+ HUE_RESP_ON=283,
+ HUE_RESP_NUM=322,
+ HUE_RESP_XY=361,
+};
+
+// Compressed from 405 to 275, -32.1%
+const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
+ "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
+ "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
+ "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
+ "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
+ "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
+ "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
+ "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
+ "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
+ "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
+ "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
+ "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
+ "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
+ "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+
+
+// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
+// "%s\"alert\":\"none\","
+// "\"effect\":\"none\","
+// "\"reachable\":true}";
+
+// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
+// ",\"type\":\"Extended color light\","
+// "\"name\":\"%s\","
+// "\"modelid\":\"%s\","
+// "\"manufacturername\":\"%s\","
+// "\"uniqueid\":\"%s\"}";
+
+// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
+// "{\"name\":\"Group 0\","
+// "\"lights\":[{l1],"
+// "\"type\":\"LightGroup\","
+// "\"action\":";
+
+// const char HueConfigResponse_JSON[] PROGMEM =
+// "{\"name\":\"Philips hue\","
+// "\"mac\":\"{ma\","
+// "\"dhcp\":true,"
+// "\"ipaddress\":\"{ip\","
+// "\"netmask\":\"{ms\","
+// "\"gateway\":\"{gw\","
+// "\"proxyaddress\":\"none\","
+// "\"proxyport\":0,"
+// "\"bridgeid\":\"{br\","
+// "\"UTC\":\"{dt\","
+// "\"whitelist\":{\"{id\":{"
+// "\"last use date\":\"{dt\","
+// "\"create date\":\"{dt\","
+// "\"name\":\"Remote\"}},"
+// "\"swversion\":\"01041302\","
+// "\"apiversion\":\"1.17.0\","
+// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
+// "\"linkbutton\":false,"
+// "\"portalservices\":false"
+// "}";
+//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
+const size_t HueConfigResponse_JSON_SIZE = 392;
+const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
+ "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
+ "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
+ "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
+ "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
+ "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
+ "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
+ "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
+ "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
+ "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
+ "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
+ "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
+ "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
+ "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
+ "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
+ "\x67\xB8";
+
+// const char HUE_ERROR_JSON[] PROGMEM =
+// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
+
+/********************************************************************************************/
+
+String GetHueDeviceId(uint16_t id)
+{
+ String deviceid = WiFi.macAddress();
+ deviceid += F(":00:11-");
+ deviceid += String(id);
+ deviceid.toLowerCase();
+ return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
+}
+
+String GetHueUserId(void)
+{
+ char userid[7];
+
+ snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
+ return String(userid);
+}
+
+void HandleUpnpSetupHue(void)
+{
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
+ String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
+ description_xml.replace(F("{x1"), WiFi.localIP().toString());
+ description_xml.replace(F("{x2"), HueUuid());
+ description_xml.replace(F("{x3"), HueSerialnumber());
+ WSSend(200, CT_XML, description_xml);
+}
+
+void HueNotImplemented(String *path)
+{
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
+
+ WSSend(200, CT_APP_JSON, PSTR("{}"));
+}
+
+void HueConfigResponse(String *response)
+{
+ *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
+ response->replace(F("{ma"), WiFi.macAddress());
+ response->replace(F("{ip"), WiFi.localIP().toString());
+ response->replace(F("{ms"), WiFi.subnetMask().toString());
+ response->replace(F("{gw"), WiFi.gatewayIP().toString());
+ response->replace(F("{br"), HueBridgeId());
+ response->replace(F("{dt"), GetDateAndTime(DT_UTC));
+ response->replace(F("{id"), GetHueUserId());
+}
+
+void HueConfig(String *path)
+{
+ String response = "";
+ HueConfigResponse(&response);
+ WSSend(200, CT_APP_JSON, response);
+}
+
+// device is forced to CT mode instead of HSB
+// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
+bool g_gotct = false;
+
+// store previously set values from the Alexa app
+// it allows to correct slight deviations from value set by the app
+// The Alexa app is very sensitive to exact values
+uint16_t prev_hue = 0;
+uint8_t prev_sat = 0;
+uint8_t prev_bri = 254;
+uint16_t prev_ct = 254;
+char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
+char prev_y_str[24] = "\0";
+
+#ifdef USE_LIGHT
+uint8_t getLocalLightSubtype(uint8_t device) {
+ if (TasmotaGlobal.light_type) {
+ if (device >= Light.device) {
+ if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
+ } else {
+ return Light.subtype; // the actual light
+ }
+ } else {
+ return LST_NONE; // relays
+ }
+ } else {
+ return LST_NONE;
+ }
+}
+
+void HueLightStatus1(uint8_t device, String *response)
+{
+ uint16_t ct = 0;
+ uint8_t color_mode;
+ String light_status = "";
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
+ // local_light_subtype simulates the Light.subtype for 'device'
+ // For relays LST_NONE, for dimmers LST_SINGLE
+ uint8_t local_light_subtype = getLocalLightSubtype(device);
+
+ bri = LightGetBri(device); // get Dimmer corrected with SetOption68
+ if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
+ if (bri < 1) bri = 1;
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
+ }
+#endif
+
+ if (TasmotaGlobal.light_type) {
+ light_state.getHSB(&hue, &sat, nullptr);
+ if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
+ hue = changeUIntScale(hue, 0, 360, 0, 65535);
+
+ if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
+ prev_x_str[0] = prev_y_str[0] = 0;
+ }
+
+ color_mode = light_state.getColorMode();
+ ct = light_state.getCT();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ }
+
+ const size_t buf_size = 256;
+ char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
+ // Brightness for all devices with PWM
+ if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
+ snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
+ }
+ if (LST_RGB <= local_light_subtype) { // colors
+ if (prev_x_str[0] && prev_y_str[0]) {
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
+ } else {
+ float x, y;
+ light_state.getXY(&x, &y);
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
+ }
+ snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
+ }
+ if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
+ snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
+ }
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
+
+ *response += buf;
+ free(buf);
+}
+
+// Check whether this device should be reported to Alexa or considered hidden.
+// Any device whose friendly name start with "$" is considered hidden
+bool HueActive(uint8_t device) {
+ if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
+ return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
+}
+
+void HueLightStatus2(uint8_t device, String *response)
+{
+ const size_t buf_size = 300;
+ char * buf = (char*) malloc(buf_size);
+ const size_t max_name_len = 32;
+ char fname[max_name_len + 1];
+
+ strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
+
+ if (device > MAX_FRIENDLYNAMES) {
+ uint32_t fname_len = strlen(fname);
+ if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
+ fname[fname_len++] = '-';
+ if (device - MAX_FRIENDLYNAMES < 10) {
+ fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
+ } else {
+ fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
+ }
+ fname[fname_len] = 0x00;
+ }
+ UnishoxStrings msg(HUE_LIGHTS);
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
+ EscapeJSONString(fname).c_str(),
+ EscapeJSONString(Settings.user_template_name).c_str(),
+ PSTR("Tasmota"),
+ GetHueDeviceId(device).c_str());
+ *response += buf;
+ free(buf);
+}
+#endif // USE_LIGHT
+
+// generate a unique lightId mixing local IP address and device number
+// it is limited to 32 devices.
+// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
+// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
+#ifndef USE_ZIGBEE
+uint32_t EncodeLightId(uint8_t relay_id)
+#else
+uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
+#endif
+{
+ uint8_t mac[6];
+ WiFi.macAddress(mac);
+ uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
+
+ if (relay_id >= 32) { // for Relay #32, we encode as 0
+ relay_id = 0;
+ }
+ if (relay_id > 15) {
+ id |= (1 << 28);
+ }
+ id |= (relay_id & 0xF);
+#ifdef USE_ZIGBEE
+ if ((z_shortaddr) && (!relay_id)) {
+ // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
+ id = (1 << 29) | z_shortaddr;
+ }
+#endif
+
+ return id;
+}
+
+
+// get hue_id and decode the relay_id
+// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
+// Zigbee:
+// If the Id encodes a Zigbee device (meaning bit 29 is set)
+// it returns 0 and sets the 'shortaddr' to the device short address
+#ifndef USE_ZIGBEE
+uint32_t DecodeLightId(uint32_t hue_id)
+#else
+uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
+#endif
+{
+ uint8_t relay_id = hue_id & 0xF;
+ if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
+ relay_id += 16;
+ }
+ if (0 == relay_id) { // special value 0 is actually relay #32
+ relay_id = 32;
+ }
+#ifdef USE_ZIGBEE
+ if (shortaddr) { *shortaddr = 0x0000; }
+ if (hue_id & (1 << 29)) {
+ // this is actually a Zigbee ID
+ if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
+ relay_id = 0;
+ }
+#endif // USE_ZIGBEE
+ return relay_id;
+}
+
+// Check if the Echo device is of 1st generation, which triggers different results
+inline uint32_t findEchoGeneration(void) {
+ // don't try to guess from User-Agent anymore but use SetOption109
+ return Settings.flag4.alexa_gen_1 ? 1 : 2;
+}
+
+void HueGlobalConfig(String *path) {
+ String response;
+
+ path->remove(0,1); // cut leading / to get
+ response = F("{\"lights\":{");
+ bool appending = false; // do we need to add a comma to append
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+ response += F("},\"groups\":{},\"schedules\":{},\"config\":");
+ HueConfigResponse(&response);
+ response += F("}");
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HueAuthentication(String *path)
+{
+ char response[38];
+
+ snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
+ WSSend(200, CT_APP_JSON, response);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
+}
+
+#ifdef USE_LIGHT
+// refactored to remove code duplicates
+void CheckHue(String * response, bool &appending) {
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ for (uint32_t i = 1; i <= maxhue; i++) {
+ if (HueActive(i)) {
+ if (appending) { *response += ","; }
+ *response += F("\"");
+ *response += EncodeLightId(i);
+ *response += F("\":{\"state\":");
+ HueLightStatus1(i, response);
+ HueLightStatus2(i, response);
+ appending = true;
+ }
+ }
+}
+
+void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint16_t ct = 0;
+ bool on = false;
+ bool resp = false; // is the response non null (add comma between parameters)
+ bool change = false; // need to change a parameter to the light
+ uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
+
+ const size_t buf_size = 100;
+ char * buf = (char*) malloc(buf_size);
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ if (Webserver->args()) {
+ response = "[";
+
+ JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
+ JsonParserObject root = parser.getRootObject();
+
+ JsonParserToken hue_on = root[PSTR("on")];
+ if (hue_on) {
+ on = hue_on.getBool();
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_ON],
+ device_id, on ? PSTR("true") : PSTR("false"));
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ if (!change) {
+ bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
+ change = true;
+ resp = true;
+ response += buf; // actually publish the state
+ }
+ } else {
+#endif
+ ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
+ response += buf;
+ resp = true;
+#ifdef USE_SHUTTER
+ }
+#endif // USE_SHUTTER
+ }
+
+ if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ light_state.getHSB(&hue, &sat, nullptr);
+ bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
+ ct = light_state.getCT();
+ uint8_t color_mode = light_state.getColorMode();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ // If LCM_BOTH == color_mode, leave g_gotct unchanged
+ } else { // treat each channel as simple dimmer
+ bri = LightGetBri(device);
+ }
+ }
+ prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
+
+ parser.setCurrent();
+ JsonParserToken hue_bri = root[PSTR("bri")];
+ if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
+ bri = hue_bri.getUInt();
+ prev_bri = bri; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("bri"), bri);
+ response += buf;
+ if (LST_SINGLE <= Light.subtype) {
+ // extend bri value if set to max
+ if (254 <= bri) { bri = 255; }
+ change = true;
+ }
+ resp = true;
+ }
+
+ // handle xy before Hue/Sat
+ // If the request contains both XY and HS, we wan't to give priority to HS
+ parser.setCurrent();
+ JsonParserToken hue_xy = root[PSTR("xy")];
+ if (hue_xy) {
+ JsonParserArray arr_xy = JsonParserArray(hue_xy);
+ JsonParserToken tok_x = arr_xy[0];
+ JsonParserToken tok_y = arr_xy[1];
+ float x = tok_x.getFloat();
+ float y = tok_y.getFloat();
+ strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
+ strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
+ uint8_t rr,gg,bb;
+ XyToRgb(x, y, &rr, &gg, &bb);
+ RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
+ prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
+ prev_sat = (sat > 254 ? 254 : sat);
+ //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_XY],
+ device_id, prev_x_str, prev_y_str);
+ response += buf;
+ g_gotct = false;
+ resp = true;
+ change = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_hue = root[PSTR("hue")];
+ if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
+ hue = hue_hue.getUInt();
+ prev_hue = hue;
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("hue"), hue);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // change range from 0..65535 to 0..360
+ hue = changeUIntScale(hue, 0, 65535, 0, 360);
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_sat = root[PSTR("sat")];
+ if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
+ sat = hue_sat.getUInt();
+ prev_sat = sat; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("sat"), sat);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // extend sat value if set to max
+ if (254 <= sat) { sat = 255; }
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_ct = root[PSTR("ct")];
+ if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
+ ct = hue_ct.getUInt();
+ prev_ct = ct; // store commande value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("ct"), ct);
+ response += buf;
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
+ g_gotct = true;
+ change = true;
+ }
+ resp = true;
+ }
+
+ if (change) {
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
+ ShutterSetPosition(device, bri * 100.0f );
+ } else
+#endif
+ if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ if (g_gotct) {
+ light_controller.changeCTB(ct, bri);
+ } else {
+ light_controller.changeHSB(hue, sat, bri);
+ }
+ LightPreparePower();
+ } else { // SetOption68 On, each channel is a dimmer
+ LightSetBri(device, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
+ } else {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
+ }
+ XdrvRulesProcess();
+ }
+ change = false;
+ }
+ response += "]";
+ if (2 == response.length()) {
+ response = msg[HUE_ERROR_JSON];
+ }
+ }
+ else {
+ response = msg[HUE_ERROR_JSON];
+ }
+ free(buf);
+}
+#endif // USE_LIGHT
+
+void HueLights(String *path)
+{
+/*
+ * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+ String response;
+ int code = 200;
+ uint8_t device = 1;
+ uint32_t device_id; // the raw device_id used by Hue emulation
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+
+ path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
+ if (path->endsWith(F("/lights"))) { // Got /lights
+ response = F("{");
+ bool appending = false;
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+#ifdef USE_SCRIPT_HUE
+ Script_Check_Hue(&response);
+#endif
+ response += F("}");
+ }
+ else if (path->endsWith(F("/state"))) { // Got ID/state
+ path->remove(0,8); // Remove /lights/
+ path->remove(path->indexOf(F("/state"))); // Remove /state
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ return ZigbeeHandleHue(shortaddr, device_id, response);
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ return Script_Handle_Hue(path);
+ }
+#endif
+#ifdef USE_LIGHT
+ if ((device >= 1) || (device <= maxhue)) {
+ HueLightsCommand(device, device_id, response);
+ }
+#endif // USE_LIGHT
+
+ }
+ else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
+ path->remove(0,8); // Remove /lights/
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ ZigbeeHueStatus(&response, shortaddr);
+ goto exit;
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
+ goto exit;
+ }
+#endif
+
+#ifdef USE_LIGHT
+ if ((device < 1) || (device > maxhue)) {
+ device = 1;
+ }
+ response += F("{\"state\":");
+ HueLightStatus1(device, &response);
+ HueLightStatus2(device, &response);
+#endif // USE_LIGHT
+ }
+ else {
+ response = F("{}");
+ code = 406;
+ }
+ exit:
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
+ WSSend(code, CT_APP_JSON, response);
+}
+
+void HueGroups(String *path)
+{
+/*
+ * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
+ */
+ String response(F("{}"));
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
+
+ if (path->endsWith(F("/0"))) {
+ UnishoxStrings msg(HUE_LIGHTS);
+ response = msg[HUE_GROUP0_STATUS_JSON];
+ String lights = F("\"1\"");
+ for (uint32_t i = 2; i <= maxhue; i++) {
+ lights += F(",\"");
+ lights += EncodeLightId(i);
+ lights += F("\"");
+ }
+
+#ifdef USE_ZIGBEE
+ ZigbeeHueGroups(&response);
+#endif // USE_ZIGBEE
+ response.replace(F("{l1"), lights);
+#ifdef USE_LIGHT
+ HueLightStatus1(1, &response);
+#endif // USE_LIGHT
+ response += F("}");
+ }
+
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HandleHueApi(String *path)
+{
+ /* HUE API uses /api// syntax. The userid is created by the echo device and
+ * on original HUE the pressed button allows for creation of this user. We simply ignore the
+ * user part and allow every caller as with Web or WeMo.
+ *
+ * (c) Heiko Krupp, 2017
+ *
+ * Hue URL
+ * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ * is converted by webserver to
+ * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+
+ uint8_t args = 0;
+
+ path->remove(0, 4); // remove /api
+ uint16_t apilen = path->length();
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
+ for (args = 0; args < Webserver->args(); args++) {
+ String json = Webserver->arg(args);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
+ }
+
+ UnishoxStrings msg(HUE_API);
+ if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
+ else if (!apilen) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
+ else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
+ else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
+ else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
+ else HueGlobalConfig(path);
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv20(uint8_t function)
+{
+ bool result = false;
+
+#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
+ if ((EMUL_HUE == Settings.flag2.emulation)) {
+#else
+ if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
+#endif
+ switch (function) {
+ case FUNC_WEB_ADD_HANDLER:
+ WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
diff --git a/.history/tasmota/xdrv_20_hue_20210321092038.ino b/.history/tasmota/xdrv_20_hue_20210321092038.ino
new file mode 100644
index 000000000..0058087a9
--- /dev/null
+++ b/.history/tasmota/xdrv_20_hue_20210321092038.ino
@@ -0,0 +1,1097 @@
+/*
+ xdrv_20_hue.ino - Philips Hue support for Tasmota
+
+ Copyright (C) 2021 Heiko Krupp and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
+/*********************************************************************************************\
+ * Philips Hue bridge emulation
+ *
+ * Hue Bridge UPNP support routines
+ * Need to send 3 response packets with varying ST and USN
+ *
+ * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
+ * Philips Lighting is 00:17:88:00:00:00
+\*********************************************************************************************/
+
+#define XDRV_20 20
+
+#include "UnishoxStrings.h"
+
+const char HUE_RESP_MSG_U[] PROGMEM =
+ //=HUE_RESP_RESPONSE
+ "HTTP/1.1 200 OK\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n"
+ "LOCATION: http://%s:80/description.xml\r\n"
+ "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+ "hue-bridgeid: %s\r\n"
+ "\0"
+ //=HUE_RESP_ST1
+ "ST: upnp:rootdevice\r\n"
+ "USN: uuid:%s::upnp:rootdevice\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST2
+ "ST: uuid:%s\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST3
+ "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0";
+
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_RESP_RESPONSE=0,
+ HUE_RESP_ST1=185,
+ HUE_RESP_ST2=240,
+ HUE_RESP_ST3=270,
+};
+
+// Compressed from 328 to 247, -24.7%
+const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
+ "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
+ "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
+ "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
+ "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
+ "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
+ "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
+ "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
+ "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
+ "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
+ "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
+ "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
+ "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
+
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+// const char HUE_RESPONSE[] PROGMEM =
+// "HTTP/1.1 200 OK\r\n"
+// "HOST: 239.255.255.250:1900\r\n"
+// "CACHE-CONTROL: max-age=100\r\n"
+// "EXT:\r\n"
+// "LOCATION: http://%s:80/description.xml\r\n"
+// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+// "hue-bridgeid: %s\r\n";
+// const char HUE_ST1[] PROGMEM =
+// "ST: upnp:rootdevice\r\n"
+// "USN: uuid:%s::upnp:rootdevice\r\n"
+// "\r\n";
+// const char HUE_ST2[] PROGMEM =
+// "ST: uuid:%s\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+// const char HUE_ST3[] PROGMEM =
+// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+
+const char HUE_API_U[] PROGMEM =
+ //=HUE_INVALID
+ "/invalid/"
+ "\0"
+ //=HUE_ROOT
+ "/"
+ "\0"
+ //=HUE_CONFIG
+ "/config"
+ "\0"
+ //=HUE_LIGHTS_API
+ "/lights"
+ "\0"
+ //=HUE_GROUPS
+ "/groups"
+ "\0"
+ //=HUE_SCHEDULES
+ "/schedules"
+ "\0"
+ //=HUE_SENSORS
+ "/sensors"
+ "\0"
+ //=HUE_SCENES
+ "/scenes"
+ "\0"
+ //=HUE_RULES
+ "/rules"
+ "\0"
+ //=HUE_RESOURCELINKS
+ "/resourcelinks"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_INVALID=0,
+ HUE_ROOT=10,
+ HUE_CONFIG=12,
+ HUE_LIGHTS_API=20,
+ HUE_GROUPS=28,
+ HUE_SCHEDULES=36,
+ HUE_SENSORS=47,
+ HUE_SCENES=56,
+ HUE_RULES=64,
+ HUE_RESOURCELINKS=71,
+};
+
+// Compressed from 86 to 74, -14.0%
+const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
+ "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
+ "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
+ "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+String HueBridgeId(void)
+{
+ String temp = WiFi.macAddress();
+ temp.replace(":", "");
+ String bridgeid = temp.substring(0, 6);
+ bridgeid += F("FFFE");
+ bridgeid += temp.substring(6);
+ return bridgeid; // 5CCF7FFFFE139F3D
+}
+
+String HueSerialnumber(void)
+{
+ String serial = WiFi.macAddress();
+ serial.replace(":", "");
+ serial.toLowerCase();
+ return serial; // 5ccf7f139f3d
+}
+
+String HueUuid(void)
+{
+ String uuid = F("f6543a06-da50-11ba-8d8f-");
+ uuid += HueSerialnumber();
+ return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
+}
+
+void HueRespondToMSearch(void)
+{
+ char message[TOPSZ];
+
+ if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
+ UnishoxStrings msg(HUE_RESP_MSG);
+ char response[320];
+ snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
+ int len = strlen(response);
+ String uuid = HueUuid();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
+ } else {
+ snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
+ }
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
+ message, udp_remote_ip.toString().c_str(), udp_remote_port);
+}
+
+/*********************************************************************************************\
+ * Hue web server additions
+\*********************************************************************************************/
+
+//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
+//Successfully compressed from 625 to 391 bytes (-37.4%)
+const size_t HUE_DESCRIPTION_XML_SIZE = 625;
+const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
+ "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
+ "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
+ "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
+ "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
+ "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
+ "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
+ "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
+ "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
+ "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
+ "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
+ "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
+ "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
+ "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
+ "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
+ "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
+ "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
+ "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
+ "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
+ "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
+// const char HUE_DESCRIPTION_XML[] PROGMEM =
+// ""
+// ""
+// ""
+// "1"
+// "0"
+// ""
+// "http://{x1:80/"
+// ""
+// "urn:schemas-upnp-org:device:Basic:1"
+// "Amazon-Echo-HA-Bridge ({x1)"
+// "Royal Philips Electronics"
+// "http://www.philips.com"
+// "Philips hue Personal Wireless Lighting"
+// "Philips hue bridge 2012"
+// "929000226503"
+// "{x3"
+// "uuid:{x2"
+// ""
+// "\r\n"
+// "\r\n";
+
+const char HUE_LIGHTS_U[] PROGMEM =
+ //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
+ "%s\"alert\":\"none\","
+ "\"effect\":\"none\","
+ "\"reachable\":true}"
+ "\0"
+ //=HUE_LIGHTS_STATUS_JSON2
+ ",\"type\":\"Extended color light\","
+ "\"name\":\"%s\","
+ "\"modelid\":\"%s\","
+ "\"manufacturername\":\"%s\","
+ "\"uniqueid\":\"%s\"}"
+ "\0"
+ //=HUE_GROUP0_STATUS_JSON
+ "{\"name\":\"Group 0\","
+ "\"lights\":[{l1],"
+ "\"type\":\"LightGroup\","
+ "\"action\":"
+ "\0"
+ //=HUE_ERROR_JSON
+ "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
+ "\0"
+ //=HUE_RESP_ON
+ "{\"success\":{\"/lights/%d/state/on\":%s}}"
+ "\0"
+ //=HUE_RESP_NUM
+ "{\"success\":{\"/lights/%d/state/%s\":%d}}"
+ "\0"
+ //=HUE_RESP_XY
+ "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
+ HUE_LIGHTS_STATUS_JSON2=51,
+ HUE_GROUP0_STATUS_JSON=150,
+ HUE_ERROR_JSON=213,
+ HUE_RESP_ON=283,
+ HUE_RESP_NUM=322,
+ HUE_RESP_XY=361,
+};
+
+// Compressed from 405 to 275, -32.1%
+const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
+ "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
+ "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
+ "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
+ "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
+ "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
+ "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
+ "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
+ "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
+ "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
+ "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
+ "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
+ "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
+ "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+
+
+// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
+// "%s\"alert\":\"none\","
+// "\"effect\":\"none\","
+// "\"reachable\":true}";
+
+// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
+// ",\"type\":\"Extended color light\","
+// "\"name\":\"%s\","
+// "\"modelid\":\"%s\","
+// "\"manufacturername\":\"%s\","
+// "\"uniqueid\":\"%s\"}";
+
+// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
+// "{\"name\":\"Group 0\","
+// "\"lights\":[{l1],"
+// "\"type\":\"LightGroup\","
+// "\"action\":";
+
+// const char HueConfigResponse_JSON[] PROGMEM =
+// "{\"name\":\"Philips hue\","
+// "\"mac\":\"{ma\","
+// "\"dhcp\":true,"
+// "\"ipaddress\":\"{ip\","
+// "\"netmask\":\"{ms\","
+// "\"gateway\":\"{gw\","
+// "\"proxyaddress\":\"none\","
+// "\"proxyport\":0,"
+// "\"bridgeid\":\"{br\","
+// "\"UTC\":\"{dt\","
+// "\"whitelist\":{\"{id\":{"
+// "\"last use date\":\"{dt\","
+// "\"create date\":\"{dt\","
+// "\"name\":\"Remote\"}},"
+// "\"swversion\":\"01041302\","
+// "\"apiversion\":\"1.17.0\","
+// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
+// "\"linkbutton\":false,"
+// "\"portalservices\":false"
+// "}";
+//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
+const size_t HueConfigResponse_JSON_SIZE = 392;
+const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
+ "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
+ "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
+ "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
+ "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
+ "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
+ "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
+ "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
+ "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
+ "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
+ "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
+ "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
+ "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
+ "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
+ "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
+ "\x67\xB8";
+
+// const char HUE_ERROR_JSON[] PROGMEM =
+// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
+
+/********************************************************************************************/
+
+String GetHueDeviceId(uint16_t id)
+{
+ String deviceid = WiFi.macAddress();
+ deviceid += F(":00:11-");
+ if(id<9) deviceid += F("0");
+ deviceid += String(id);
+ deviceid.toLowerCase();
+ return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
+}
+
+String GetHueUserId(void)
+{
+ char userid[7];
+
+ snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
+ return String(userid);
+}
+
+void HandleUpnpSetupHue(void)
+{
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
+ String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
+ description_xml.replace(F("{x1"), WiFi.localIP().toString());
+ description_xml.replace(F("{x2"), HueUuid());
+ description_xml.replace(F("{x3"), HueSerialnumber());
+ WSSend(200, CT_XML, description_xml);
+}
+
+void HueNotImplemented(String *path)
+{
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
+
+ WSSend(200, CT_APP_JSON, PSTR("{}"));
+}
+
+void HueConfigResponse(String *response)
+{
+ *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
+ response->replace(F("{ma"), WiFi.macAddress());
+ response->replace(F("{ip"), WiFi.localIP().toString());
+ response->replace(F("{ms"), WiFi.subnetMask().toString());
+ response->replace(F("{gw"), WiFi.gatewayIP().toString());
+ response->replace(F("{br"), HueBridgeId());
+ response->replace(F("{dt"), GetDateAndTime(DT_UTC));
+ response->replace(F("{id"), GetHueUserId());
+}
+
+void HueConfig(String *path)
+{
+ String response = "";
+ HueConfigResponse(&response);
+ WSSend(200, CT_APP_JSON, response);
+}
+
+// device is forced to CT mode instead of HSB
+// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
+bool g_gotct = false;
+
+// store previously set values from the Alexa app
+// it allows to correct slight deviations from value set by the app
+// The Alexa app is very sensitive to exact values
+uint16_t prev_hue = 0;
+uint8_t prev_sat = 0;
+uint8_t prev_bri = 254;
+uint16_t prev_ct = 254;
+char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
+char prev_y_str[24] = "\0";
+
+#ifdef USE_LIGHT
+uint8_t getLocalLightSubtype(uint8_t device) {
+ if (TasmotaGlobal.light_type) {
+ if (device >= Light.device) {
+ if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
+ } else {
+ return Light.subtype; // the actual light
+ }
+ } else {
+ return LST_NONE; // relays
+ }
+ } else {
+ return LST_NONE;
+ }
+}
+
+void HueLightStatus1(uint8_t device, String *response)
+{
+ uint16_t ct = 0;
+ uint8_t color_mode;
+ String light_status = "";
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
+ // local_light_subtype simulates the Light.subtype for 'device'
+ // For relays LST_NONE, for dimmers LST_SINGLE
+ uint8_t local_light_subtype = getLocalLightSubtype(device);
+
+ bri = LightGetBri(device); // get Dimmer corrected with SetOption68
+ if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
+ if (bri < 1) bri = 1;
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
+ }
+#endif
+
+ if (TasmotaGlobal.light_type) {
+ light_state.getHSB(&hue, &sat, nullptr);
+ if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
+ hue = changeUIntScale(hue, 0, 360, 0, 65535);
+
+ if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
+ prev_x_str[0] = prev_y_str[0] = 0;
+ }
+
+ color_mode = light_state.getColorMode();
+ ct = light_state.getCT();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ }
+
+ const size_t buf_size = 256;
+ char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
+ // Brightness for all devices with PWM
+ if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
+ snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
+ }
+ if (LST_RGB <= local_light_subtype) { // colors
+ if (prev_x_str[0] && prev_y_str[0]) {
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
+ } else {
+ float x, y;
+ light_state.getXY(&x, &y);
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
+ }
+ snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
+ }
+ if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
+ snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
+ }
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
+
+ *response += buf;
+ free(buf);
+}
+
+// Check whether this device should be reported to Alexa or considered hidden.
+// Any device whose friendly name start with "$" is considered hidden
+bool HueActive(uint8_t device) {
+ if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
+ return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
+}
+
+void HueLightStatus2(uint8_t device, String *response)
+{
+ const size_t buf_size = 300;
+ char * buf = (char*) malloc(buf_size);
+ const size_t max_name_len = 32;
+ char fname[max_name_len + 1];
+
+ strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
+
+ if (device > MAX_FRIENDLYNAMES) {
+ uint32_t fname_len = strlen(fname);
+ if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
+ fname[fname_len++] = '-';
+ if (device - MAX_FRIENDLYNAMES < 10) {
+ fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
+ } else {
+ fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
+ }
+ fname[fname_len] = 0x00;
+ }
+ UnishoxStrings msg(HUE_LIGHTS);
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
+ EscapeJSONString(fname).c_str(),
+ EscapeJSONString(Settings.user_template_name).c_str(),
+ PSTR("Tasmota"),
+ GetHueDeviceId(device).c_str());
+ *response += buf;
+ free(buf);
+}
+#endif // USE_LIGHT
+
+// generate a unique lightId mixing local IP address and device number
+// it is limited to 32 devices.
+// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
+// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
+#ifndef USE_ZIGBEE
+uint32_t EncodeLightId(uint8_t relay_id)
+#else
+uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
+#endif
+{
+ uint8_t mac[6];
+ WiFi.macAddress(mac);
+ uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
+
+ if (relay_id >= 32) { // for Relay #32, we encode as 0
+ relay_id = 0;
+ }
+ if (relay_id > 15) {
+ id |= (1 << 28);
+ }
+ id |= (relay_id & 0xF);
+#ifdef USE_ZIGBEE
+ if ((z_shortaddr) && (!relay_id)) {
+ // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
+ id = (1 << 29) | z_shortaddr;
+ }
+#endif
+
+ return id;
+}
+
+
+// get hue_id and decode the relay_id
+// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
+// Zigbee:
+// If the Id encodes a Zigbee device (meaning bit 29 is set)
+// it returns 0 and sets the 'shortaddr' to the device short address
+#ifndef USE_ZIGBEE
+uint32_t DecodeLightId(uint32_t hue_id)
+#else
+uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
+#endif
+{
+ uint8_t relay_id = hue_id & 0xF;
+ if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
+ relay_id += 16;
+ }
+ if (0 == relay_id) { // special value 0 is actually relay #32
+ relay_id = 32;
+ }
+#ifdef USE_ZIGBEE
+ if (shortaddr) { *shortaddr = 0x0000; }
+ if (hue_id & (1 << 29)) {
+ // this is actually a Zigbee ID
+ if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
+ relay_id = 0;
+ }
+#endif // USE_ZIGBEE
+ return relay_id;
+}
+
+// Check if the Echo device is of 1st generation, which triggers different results
+inline uint32_t findEchoGeneration(void) {
+ // don't try to guess from User-Agent anymore but use SetOption109
+ return Settings.flag4.alexa_gen_1 ? 1 : 2;
+}
+
+void HueGlobalConfig(String *path) {
+ String response;
+
+ path->remove(0,1); // cut leading / to get
+ response = F("{\"lights\":{");
+ bool appending = false; // do we need to add a comma to append
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+ response += F("},\"groups\":{},\"schedules\":{},\"config\":");
+ HueConfigResponse(&response);
+ response += F("}");
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HueAuthentication(String *path)
+{
+ char response[38];
+
+ snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
+ WSSend(200, CT_APP_JSON, response);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
+}
+
+#ifdef USE_LIGHT
+// refactored to remove code duplicates
+void CheckHue(String * response, bool &appending) {
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ for (uint32_t i = 1; i <= maxhue; i++) {
+ if (HueActive(i)) {
+ if (appending) { *response += ","; }
+ *response += F("\"");
+ *response += EncodeLightId(i);
+ *response += F("\":{\"state\":");
+ HueLightStatus1(i, response);
+ HueLightStatus2(i, response);
+ appending = true;
+ }
+ }
+}
+
+void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint16_t ct = 0;
+ bool on = false;
+ bool resp = false; // is the response non null (add comma between parameters)
+ bool change = false; // need to change a parameter to the light
+ uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
+
+ const size_t buf_size = 100;
+ char * buf = (char*) malloc(buf_size);
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ if (Webserver->args()) {
+ response = "[";
+
+ JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
+ JsonParserObject root = parser.getRootObject();
+
+ JsonParserToken hue_on = root[PSTR("on")];
+ if (hue_on) {
+ on = hue_on.getBool();
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_ON],
+ device_id, on ? PSTR("true") : PSTR("false"));
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ if (!change) {
+ bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
+ change = true;
+ resp = true;
+ response += buf; // actually publish the state
+ }
+ } else {
+#endif
+ ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
+ response += buf;
+ resp = true;
+#ifdef USE_SHUTTER
+ }
+#endif // USE_SHUTTER
+ }
+
+ if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ light_state.getHSB(&hue, &sat, nullptr);
+ bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
+ ct = light_state.getCT();
+ uint8_t color_mode = light_state.getColorMode();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ // If LCM_BOTH == color_mode, leave g_gotct unchanged
+ } else { // treat each channel as simple dimmer
+ bri = LightGetBri(device);
+ }
+ }
+ prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
+
+ parser.setCurrent();
+ JsonParserToken hue_bri = root[PSTR("bri")];
+ if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
+ bri = hue_bri.getUInt();
+ prev_bri = bri; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("bri"), bri);
+ response += buf;
+ if (LST_SINGLE <= Light.subtype) {
+ // extend bri value if set to max
+ if (254 <= bri) { bri = 255; }
+ change = true;
+ }
+ resp = true;
+ }
+
+ // handle xy before Hue/Sat
+ // If the request contains both XY and HS, we wan't to give priority to HS
+ parser.setCurrent();
+ JsonParserToken hue_xy = root[PSTR("xy")];
+ if (hue_xy) {
+ JsonParserArray arr_xy = JsonParserArray(hue_xy);
+ JsonParserToken tok_x = arr_xy[0];
+ JsonParserToken tok_y = arr_xy[1];
+ float x = tok_x.getFloat();
+ float y = tok_y.getFloat();
+ strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
+ strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
+ uint8_t rr,gg,bb;
+ XyToRgb(x, y, &rr, &gg, &bb);
+ RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
+ prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
+ prev_sat = (sat > 254 ? 254 : sat);
+ //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_XY],
+ device_id, prev_x_str, prev_y_str);
+ response += buf;
+ g_gotct = false;
+ resp = true;
+ change = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_hue = root[PSTR("hue")];
+ if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
+ hue = hue_hue.getUInt();
+ prev_hue = hue;
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("hue"), hue);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // change range from 0..65535 to 0..360
+ hue = changeUIntScale(hue, 0, 65535, 0, 360);
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_sat = root[PSTR("sat")];
+ if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
+ sat = hue_sat.getUInt();
+ prev_sat = sat; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("sat"), sat);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // extend sat value if set to max
+ if (254 <= sat) { sat = 255; }
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_ct = root[PSTR("ct")];
+ if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
+ ct = hue_ct.getUInt();
+ prev_ct = ct; // store commande value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("ct"), ct);
+ response += buf;
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
+ g_gotct = true;
+ change = true;
+ }
+ resp = true;
+ }
+
+ if (change) {
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
+ ShutterSetPosition(device, bri * 100.0f );
+ } else
+#endif
+ if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ if (g_gotct) {
+ light_controller.changeCTB(ct, bri);
+ } else {
+ light_controller.changeHSB(hue, sat, bri);
+ }
+ LightPreparePower();
+ } else { // SetOption68 On, each channel is a dimmer
+ LightSetBri(device, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
+ } else {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
+ }
+ XdrvRulesProcess();
+ }
+ change = false;
+ }
+ response += "]";
+ if (2 == response.length()) {
+ response = msg[HUE_ERROR_JSON];
+ }
+ }
+ else {
+ response = msg[HUE_ERROR_JSON];
+ }
+ free(buf);
+}
+#endif // USE_LIGHT
+
+void HueLights(String *path)
+{
+/*
+ * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+ String response;
+ int code = 200;
+ uint8_t device = 1;
+ uint32_t device_id; // the raw device_id used by Hue emulation
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+
+ path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
+ if (path->endsWith(F("/lights"))) { // Got /lights
+ response = F("{");
+ bool appending = false;
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+#ifdef USE_SCRIPT_HUE
+ Script_Check_Hue(&response);
+#endif
+ response += F("}");
+ }
+ else if (path->endsWith(F("/state"))) { // Got ID/state
+ path->remove(0,8); // Remove /lights/
+ path->remove(path->indexOf(F("/state"))); // Remove /state
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ return ZigbeeHandleHue(shortaddr, device_id, response);
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ return Script_Handle_Hue(path);
+ }
+#endif
+#ifdef USE_LIGHT
+ if ((device >= 1) || (device <= maxhue)) {
+ HueLightsCommand(device, device_id, response);
+ }
+#endif // USE_LIGHT
+
+ }
+ else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
+ path->remove(0,8); // Remove /lights/
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ ZigbeeHueStatus(&response, shortaddr);
+ goto exit;
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
+ goto exit;
+ }
+#endif
+
+#ifdef USE_LIGHT
+ if ((device < 1) || (device > maxhue)) {
+ device = 1;
+ }
+ response += F("{\"state\":");
+ HueLightStatus1(device, &response);
+ HueLightStatus2(device, &response);
+#endif // USE_LIGHT
+ }
+ else {
+ response = F("{}");
+ code = 406;
+ }
+ exit:
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
+ WSSend(code, CT_APP_JSON, response);
+}
+
+void HueGroups(String *path)
+{
+/*
+ * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
+ */
+ String response(F("{}"));
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
+
+ if (path->endsWith(F("/0"))) {
+ UnishoxStrings msg(HUE_LIGHTS);
+ response = msg[HUE_GROUP0_STATUS_JSON];
+ String lights = F("\"1\"");
+ for (uint32_t i = 2; i <= maxhue; i++) {
+ lights += F(",\"");
+ lights += EncodeLightId(i);
+ lights += F("\"");
+ }
+
+#ifdef USE_ZIGBEE
+ ZigbeeHueGroups(&response);
+#endif // USE_ZIGBEE
+ response.replace(F("{l1"), lights);
+#ifdef USE_LIGHT
+ HueLightStatus1(1, &response);
+#endif // USE_LIGHT
+ response += F("}");
+ }
+
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HandleHueApi(String *path)
+{
+ /* HUE API uses /api// syntax. The userid is created by the echo device and
+ * on original HUE the pressed button allows for creation of this user. We simply ignore the
+ * user part and allow every caller as with Web or WeMo.
+ *
+ * (c) Heiko Krupp, 2017
+ *
+ * Hue URL
+ * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ * is converted by webserver to
+ * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+
+ uint8_t args = 0;
+
+ path->remove(0, 4); // remove /api
+ uint16_t apilen = path->length();
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
+ for (args = 0; args < Webserver->args(); args++) {
+ String json = Webserver->arg(args);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
+ }
+
+ UnishoxStrings msg(HUE_API);
+ if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
+ else if (!apilen) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
+ else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
+ else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
+ else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
+ else HueGlobalConfig(path);
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv20(uint8_t function)
+{
+ bool result = false;
+
+#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
+ if ((EMUL_HUE == Settings.flag2.emulation)) {
+#else
+ if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
+#endif
+ switch (function) {
+ case FUNC_WEB_ADD_HANDLER:
+ WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
diff --git a/.history/tasmota/xdrv_20_hue_20210321092519.ino b/.history/tasmota/xdrv_20_hue_20210321092519.ino
new file mode 100644
index 000000000..ac48628df
--- /dev/null
+++ b/.history/tasmota/xdrv_20_hue_20210321092519.ino
@@ -0,0 +1,1097 @@
+/*
+ xdrv_20_hue.ino - Philips Hue support for Tasmota
+
+ Copyright (C) 2021 Heiko Krupp and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
+/*********************************************************************************************\
+ * Philips Hue bridge emulation
+ *
+ * Hue Bridge UPNP support routines
+ * Need to send 3 response packets with varying ST and USN
+ *
+ * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
+ * Philips Lighting is 00:17:88:00:00:00
+\*********************************************************************************************/
+
+#define XDRV_20 20
+
+#include "UnishoxStrings.h"
+
+const char HUE_RESP_MSG_U[] PROGMEM =
+ //=HUE_RESP_RESPONSE
+ "HTTP/1.1 200 OK\r\n"
+ "HOST: 239.255.255.250:1900\r\n"
+ "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n"
+ "LOCATION: http://%s:80/description.xml\r\n"
+ "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+ "hue-bridgeid: %s\r\n"
+ "\0"
+ //=HUE_RESP_ST1
+ "ST: upnp:rootdevice\r\n"
+ "USN: uuid:%s::upnp:rootdevice\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST2
+ "ST: uuid:%s\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0"
+ //=HUE_RESP_ST3
+ "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+ "USN: uuid:%s\r\n"
+ "\r\n"
+ "\0";
+
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_RESP_RESPONSE=0,
+ HUE_RESP_ST1=185,
+ HUE_RESP_ST2=240,
+ HUE_RESP_ST3=270,
+};
+
+// Compressed from 328 to 247, -24.7%
+const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
+ "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
+ "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
+ "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
+ "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
+ "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
+ "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
+ "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
+ "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
+ "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
+ "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
+ "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
+ "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
+
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+// const char HUE_RESPONSE[] PROGMEM =
+// "HTTP/1.1 200 OK\r\n"
+// "HOST: 239.255.255.250:1900\r\n"
+// "CACHE-CONTROL: max-age=100\r\n"
+// "EXT:\r\n"
+// "LOCATION: http://%s:80/description.xml\r\n"
+// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
+// "hue-bridgeid: %s\r\n";
+// const char HUE_ST1[] PROGMEM =
+// "ST: upnp:rootdevice\r\n"
+// "USN: uuid:%s::upnp:rootdevice\r\n"
+// "\r\n";
+// const char HUE_ST2[] PROGMEM =
+// "ST: uuid:%s\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+// const char HUE_ST3[] PROGMEM =
+// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+// "USN: uuid:%s\r\n"
+// "\r\n";
+
+const char HUE_API_U[] PROGMEM =
+ //=HUE_INVALID
+ "/invalid/"
+ "\0"
+ //=HUE_ROOT
+ "/"
+ "\0"
+ //=HUE_CONFIG
+ "/config"
+ "\0"
+ //=HUE_LIGHTS_API
+ "/lights"
+ "\0"
+ //=HUE_GROUPS
+ "/groups"
+ "\0"
+ //=HUE_SCHEDULES
+ "/schedules"
+ "\0"
+ //=HUE_SENSORS
+ "/sensors"
+ "\0"
+ //=HUE_SCENES
+ "/scenes"
+ "\0"
+ //=HUE_RULES
+ "/rules"
+ "\0"
+ //=HUE_RESOURCELINKS
+ "/resourcelinks"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_INVALID=0,
+ HUE_ROOT=10,
+ HUE_CONFIG=12,
+ HUE_LIGHTS_API=20,
+ HUE_GROUPS=28,
+ HUE_SCHEDULES=36,
+ HUE_SENSORS=47,
+ HUE_SCENES=56,
+ HUE_RULES=64,
+ HUE_RESOURCELINKS=71,
+};
+
+// Compressed from 86 to 74, -14.0%
+const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
+ "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
+ "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
+ "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+String HueBridgeId(void)
+{
+ String temp = WiFi.macAddress();
+ temp.replace(":", "");
+ String bridgeid = temp.substring(0, 6);
+ bridgeid += F("FFFE");
+ bridgeid += temp.substring(6);
+ return bridgeid; // 5CCF7FFFFE139F3D
+}
+
+String HueSerialnumber(void)
+{
+ String serial = WiFi.macAddress();
+ serial.replace(":", "");
+ serial.toLowerCase();
+ return serial; // 5ccf7f139f3d
+}
+
+String HueUuid(void)
+{
+ String uuid = F("f6543a06-da50-11ba-8d8f-");
+ uuid += HueSerialnumber();
+ return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
+}
+
+void HueRespondToMSearch(void)
+{
+ char message[TOPSZ];
+
+ if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
+ UnishoxStrings msg(HUE_RESP_MSG);
+ char response[320];
+ snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
+ int len = strlen(response);
+ String uuid = HueUuid();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
+ PortUdp.write(response);
+ PortUdp.endPacket();
+
+ snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
+ } else {
+ snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
+ }
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
+ message, udp_remote_ip.toString().c_str(), udp_remote_port);
+}
+
+/*********************************************************************************************\
+ * Hue web server additions
+\*********************************************************************************************/
+
+//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
+//Successfully compressed from 625 to 391 bytes (-37.4%)
+const size_t HUE_DESCRIPTION_XML_SIZE = 625;
+const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
+ "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
+ "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
+ "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
+ "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
+ "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
+ "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
+ "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
+ "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
+ "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
+ "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
+ "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
+ "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
+ "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
+ "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
+ "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
+ "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
+ "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
+ "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
+ "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
+// const char HUE_DESCRIPTION_XML[] PROGMEM =
+// ""
+// ""
+// ""
+// "1"
+// "0"
+// ""
+// "http://{x1:80/"
+// ""
+// "urn:schemas-upnp-org:device:Basic:1"
+// "Amazon-Echo-HA-Bridge ({x1)"
+// "Royal Philips Electronics"
+// "http://www.philips.com"
+// "Philips hue Personal Wireless Lighting"
+// "Philips hue bridge 2012"
+// "929000226503"
+// "{x3"
+// "uuid:{x2"
+// ""
+// "\r\n"
+// "\r\n";
+
+const char HUE_LIGHTS_U[] PROGMEM =
+ //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
+ "%s\"alert\":\"none\","
+ "\"effect\":\"none\","
+ "\"reachable\":true}"
+ "\0"
+ //=HUE_LIGHTS_STATUS_JSON2
+ ",\"type\":\"Extended color light\","
+ "\"name\":\"%s\","
+ "\"modelid\":\"%s\","
+ "\"manufacturername\":\"%s\","
+ "\"uniqueid\":\"%s\"}"
+ "\0"
+ //=HUE_GROUP0_STATUS_JSON
+ "{\"name\":\"Group 0\","
+ "\"lights\":[{l1],"
+ "\"type\":\"LightGroup\","
+ "\"action\":"
+ "\0"
+ //=HUE_ERROR_JSON
+ "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
+ "\0"
+ //=HUE_RESP_ON
+ "{\"success\":{\"/lights/%d/state/on\":%s}}"
+ "\0"
+ //=HUE_RESP_NUM
+ "{\"success\":{\"/lights/%d/state/%s\":%d}}"
+ "\0"
+ //=HUE_RESP_XY
+ "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
+ "\0"
+ ;
+
+// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
+// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
+enum {
+ HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
+ HUE_LIGHTS_STATUS_JSON2=51,
+ HUE_GROUP0_STATUS_JSON=150,
+ HUE_ERROR_JSON=213,
+ HUE_RESP_ON=283,
+ HUE_RESP_NUM=322,
+ HUE_RESP_XY=361,
+};
+
+// Compressed from 405 to 275, -32.1%
+const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
+ "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
+ "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
+ "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
+ "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
+ "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
+ "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
+ "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
+ "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
+ "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
+ "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
+ "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
+ "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
+ "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
+// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
+// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
+// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+
+
+// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
+// "%s\"alert\":\"none\","
+// "\"effect\":\"none\","
+// "\"reachable\":true}";
+
+// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
+// ",\"type\":\"Extended color light\","
+// "\"name\":\"%s\","
+// "\"modelid\":\"%s\","
+// "\"manufacturername\":\"%s\","
+// "\"uniqueid\":\"%s\"}";
+
+// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
+// "{\"name\":\"Group 0\","
+// "\"lights\":[{l1],"
+// "\"type\":\"LightGroup\","
+// "\"action\":";
+
+// const char HueConfigResponse_JSON[] PROGMEM =
+// "{\"name\":\"Philips hue\","
+// "\"mac\":\"{ma\","
+// "\"dhcp\":true,"
+// "\"ipaddress\":\"{ip\","
+// "\"netmask\":\"{ms\","
+// "\"gateway\":\"{gw\","
+// "\"proxyaddress\":\"none\","
+// "\"proxyport\":0,"
+// "\"bridgeid\":\"{br\","
+// "\"UTC\":\"{dt\","
+// "\"whitelist\":{\"{id\":{"
+// "\"last use date\":\"{dt\","
+// "\"create date\":\"{dt\","
+// "\"name\":\"Remote\"}},"
+// "\"swversion\":\"01041302\","
+// "\"apiversion\":\"1.17.0\","
+// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
+// "\"linkbutton\":false,"
+// "\"portalservices\":false"
+// "}";
+//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
+const size_t HueConfigResponse_JSON_SIZE = 392;
+const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
+ "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
+ "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
+ "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
+ "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
+ "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
+ "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
+ "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
+ "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
+ "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
+ "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
+ "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
+ "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
+ "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
+ "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
+ "\x67\xB8";
+
+// const char HUE_ERROR_JSON[] PROGMEM =
+// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
+
+/********************************************************************************************/
+
+String GetHueDeviceId(uint16_t id)
+{
+ String deviceid = WiFi.macAddress();
+ deviceid += F(":00:11-");
+ if(id<9) deviceid += F("0");
+ deviceid += String(id);
+ deviceid.toLowerCase();
+ return deviceid; // 5c:cf:7f:13:9f:3d:00:11-01
+}
+
+String GetHueUserId(void)
+{
+ char userid[7];
+
+ snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
+ return String(userid);
+}
+
+void HandleUpnpSetupHue(void)
+{
+ AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
+ String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
+ description_xml.replace(F("{x1"), WiFi.localIP().toString());
+ description_xml.replace(F("{x2"), HueUuid());
+ description_xml.replace(F("{x3"), HueSerialnumber());
+ WSSend(200, CT_XML, description_xml);
+}
+
+void HueNotImplemented(String *path)
+{
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
+
+ WSSend(200, CT_APP_JSON, PSTR("{}"));
+}
+
+void HueConfigResponse(String *response)
+{
+ *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
+ response->replace(F("{ma"), WiFi.macAddress());
+ response->replace(F("{ip"), WiFi.localIP().toString());
+ response->replace(F("{ms"), WiFi.subnetMask().toString());
+ response->replace(F("{gw"), WiFi.gatewayIP().toString());
+ response->replace(F("{br"), HueBridgeId());
+ response->replace(F("{dt"), GetDateAndTime(DT_UTC));
+ response->replace(F("{id"), GetHueUserId());
+}
+
+void HueConfig(String *path)
+{
+ String response = "";
+ HueConfigResponse(&response);
+ WSSend(200, CT_APP_JSON, response);
+}
+
+// device is forced to CT mode instead of HSB
+// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
+bool g_gotct = false;
+
+// store previously set values from the Alexa app
+// it allows to correct slight deviations from value set by the app
+// The Alexa app is very sensitive to exact values
+uint16_t prev_hue = 0;
+uint8_t prev_sat = 0;
+uint8_t prev_bri = 254;
+uint16_t prev_ct = 254;
+char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
+char prev_y_str[24] = "\0";
+
+#ifdef USE_LIGHT
+uint8_t getLocalLightSubtype(uint8_t device) {
+ if (TasmotaGlobal.light_type) {
+ if (device >= Light.device) {
+ if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
+ } else {
+ return Light.subtype; // the actual light
+ }
+ } else {
+ return LST_NONE; // relays
+ }
+ } else {
+ return LST_NONE;
+ }
+}
+
+void HueLightStatus1(uint8_t device, String *response)
+{
+ uint16_t ct = 0;
+ uint8_t color_mode;
+ String light_status = "";
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
+ // local_light_subtype simulates the Light.subtype for 'device'
+ // For relays LST_NONE, for dimmers LST_SINGLE
+ uint8_t local_light_subtype = getLocalLightSubtype(device);
+
+ bri = LightGetBri(device); // get Dimmer corrected with SetOption68
+ if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
+ if (bri < 1) bri = 1;
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
+ }
+#endif
+
+ if (TasmotaGlobal.light_type) {
+ light_state.getHSB(&hue, &sat, nullptr);
+ if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
+ hue = changeUIntScale(hue, 0, 360, 0, 65535);
+
+ if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
+ prev_x_str[0] = prev_y_str[0] = 0;
+ }
+
+ color_mode = light_state.getColorMode();
+ ct = light_state.getCT();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ }
+
+ const size_t buf_size = 256;
+ char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
+ // Brightness for all devices with PWM
+ if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
+ snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
+ }
+ if (LST_RGB <= local_light_subtype) { // colors
+ if (prev_x_str[0] && prev_y_str[0]) {
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
+ } else {
+ float x, y;
+ light_state.getXY(&x, &y);
+ snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
+ }
+ snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
+ }
+ if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
+ snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
+ }
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
+
+ *response += buf;
+ free(buf);
+}
+
+// Check whether this device should be reported to Alexa or considered hidden.
+// Any device whose friendly name start with "$" is considered hidden
+bool HueActive(uint8_t device) {
+ if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
+ return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
+}
+
+void HueLightStatus2(uint8_t device, String *response)
+{
+ const size_t buf_size = 300;
+ char * buf = (char*) malloc(buf_size);
+ const size_t max_name_len = 32;
+ char fname[max_name_len + 1];
+
+ strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
+
+ if (device > MAX_FRIENDLYNAMES) {
+ uint32_t fname_len = strlen(fname);
+ if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
+ fname[fname_len++] = '-';
+ if (device - MAX_FRIENDLYNAMES < 10) {
+ fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
+ } else {
+ fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
+ }
+ fname[fname_len] = 0x00;
+ }
+ UnishoxStrings msg(HUE_LIGHTS);
+ snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
+ EscapeJSONString(fname).c_str(),
+ EscapeJSONString(Settings.user_template_name).c_str(),
+ PSTR("Tasmota"),
+ GetHueDeviceId(device).c_str());
+ *response += buf;
+ free(buf);
+}
+#endif // USE_LIGHT
+
+// generate a unique lightId mixing local IP address and device number
+// it is limited to 32 devices.
+// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
+// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
+#ifndef USE_ZIGBEE
+uint32_t EncodeLightId(uint8_t relay_id)
+#else
+uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
+#endif
+{
+ uint8_t mac[6];
+ WiFi.macAddress(mac);
+ uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
+
+ if (relay_id >= 32) { // for Relay #32, we encode as 0
+ relay_id = 0;
+ }
+ if (relay_id > 15) {
+ id |= (1 << 28);
+ }
+ id |= (relay_id & 0xF);
+#ifdef USE_ZIGBEE
+ if ((z_shortaddr) && (!relay_id)) {
+ // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
+ id = (1 << 29) | z_shortaddr;
+ }
+#endif
+
+ return id;
+}
+
+
+// get hue_id and decode the relay_id
+// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
+// Zigbee:
+// If the Id encodes a Zigbee device (meaning bit 29 is set)
+// it returns 0 and sets the 'shortaddr' to the device short address
+#ifndef USE_ZIGBEE
+uint32_t DecodeLightId(uint32_t hue_id)
+#else
+uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
+#endif
+{
+ uint8_t relay_id = hue_id & 0xF;
+ if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
+ relay_id += 16;
+ }
+ if (0 == relay_id) { // special value 0 is actually relay #32
+ relay_id = 32;
+ }
+#ifdef USE_ZIGBEE
+ if (shortaddr) { *shortaddr = 0x0000; }
+ if (hue_id & (1 << 29)) {
+ // this is actually a Zigbee ID
+ if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
+ relay_id = 0;
+ }
+#endif // USE_ZIGBEE
+ return relay_id;
+}
+
+// Check if the Echo device is of 1st generation, which triggers different results
+inline uint32_t findEchoGeneration(void) {
+ // don't try to guess from User-Agent anymore but use SetOption109
+ return Settings.flag4.alexa_gen_1 ? 1 : 2;
+}
+
+void HueGlobalConfig(String *path) {
+ String response;
+
+ path->remove(0,1); // cut leading / to get
+ response = F("{\"lights\":{");
+ bool appending = false; // do we need to add a comma to append
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+ response += F("},\"groups\":{},\"schedules\":{},\"config\":");
+ HueConfigResponse(&response);
+ response += F("}");
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HueAuthentication(String *path)
+{
+ char response[38];
+
+ snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
+ WSSend(200, CT_APP_JSON, response);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
+}
+
+#ifdef USE_LIGHT
+// refactored to remove code duplicates
+void CheckHue(String * response, bool &appending) {
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ for (uint32_t i = 1; i <= maxhue; i++) {
+ if (HueActive(i)) {
+ if (appending) { *response += ","; }
+ *response += F("\"");
+ *response += EncodeLightId(i);
+ *response += F("\":{\"state\":");
+ HueLightStatus1(i, response);
+ HueLightStatus2(i, response);
+ appending = true;
+ }
+ }
+}
+
+void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
+ uint16_t hue = 0;
+ uint8_t sat = 0;
+ uint8_t bri = 254;
+ uint16_t ct = 0;
+ bool on = false;
+ bool resp = false; // is the response non null (add comma between parameters)
+ bool change = false; // need to change a parameter to the light
+ uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
+
+ const size_t buf_size = 100;
+ char * buf = (char*) malloc(buf_size);
+ UnishoxStrings msg(HUE_LIGHTS);
+
+ if (Webserver->args()) {
+ response = "[";
+
+ JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
+ JsonParserObject root = parser.getRootObject();
+
+ JsonParserToken hue_on = root[PSTR("on")];
+ if (hue_on) {
+ on = hue_on.getBool();
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_ON],
+ device_id, on ? PSTR("true") : PSTR("false"));
+
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ if (!change) {
+ bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
+ change = true;
+ resp = true;
+ response += buf; // actually publish the state
+ }
+ } else {
+#endif
+ ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
+ response += buf;
+ resp = true;
+#ifdef USE_SHUTTER
+ }
+#endif // USE_SHUTTER
+ }
+
+ if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ light_state.getHSB(&hue, &sat, nullptr);
+ bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
+ ct = light_state.getCT();
+ uint8_t color_mode = light_state.getColorMode();
+ if (LCM_RGB == color_mode) { g_gotct = false; }
+ if (LCM_CT == color_mode) { g_gotct = true; }
+ // If LCM_BOTH == color_mode, leave g_gotct unchanged
+ } else { // treat each channel as simple dimmer
+ bri = LightGetBri(device);
+ }
+ }
+ prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
+
+ parser.setCurrent();
+ JsonParserToken hue_bri = root[PSTR("bri")];
+ if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
+ bri = hue_bri.getUInt();
+ prev_bri = bri; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("bri"), bri);
+ response += buf;
+ if (LST_SINGLE <= Light.subtype) {
+ // extend bri value if set to max
+ if (254 <= bri) { bri = 255; }
+ change = true;
+ }
+ resp = true;
+ }
+
+ // handle xy before Hue/Sat
+ // If the request contains both XY and HS, we wan't to give priority to HS
+ parser.setCurrent();
+ JsonParserToken hue_xy = root[PSTR("xy")];
+ if (hue_xy) {
+ JsonParserArray arr_xy = JsonParserArray(hue_xy);
+ JsonParserToken tok_x = arr_xy[0];
+ JsonParserToken tok_y = arr_xy[1];
+ float x = tok_x.getFloat();
+ float y = tok_y.getFloat();
+ strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
+ strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
+ uint8_t rr,gg,bb;
+ XyToRgb(x, y, &rr, &gg, &bb);
+ RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
+ prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
+ prev_sat = (sat > 254 ? 254 : sat);
+ //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_XY],
+ device_id, prev_x_str, prev_y_str);
+ response += buf;
+ g_gotct = false;
+ resp = true;
+ change = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_hue = root[PSTR("hue")];
+ if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
+ hue = hue_hue.getUInt();
+ prev_hue = hue;
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("hue"), hue);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // change range from 0..65535 to 0..360
+ hue = changeUIntScale(hue, 0, 65535, 0, 360);
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_sat = root[PSTR("sat")];
+ if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
+ sat = hue_sat.getUInt();
+ prev_sat = sat; // store command value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("sat"), sat);
+ response += buf;
+ if (LST_RGB <= Light.subtype) {
+ // extend sat value if set to max
+ if (254 <= sat) { sat = 255; }
+ g_gotct = false;
+ change = true;
+ }
+ resp = true;
+ }
+
+ parser.setCurrent();
+ JsonParserToken hue_ct = root[PSTR("ct")];
+ if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
+ ct = hue_ct.getUInt();
+ prev_ct = ct; // store commande value
+ if (resp) { response += ","; }
+ snprintf_P(buf, buf_size,
+ msg[HUE_RESP_NUM],
+ device_id, PSTR("ct"), ct);
+ response += buf;
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
+ g_gotct = true;
+ change = true;
+ }
+ resp = true;
+ }
+
+ if (change) {
+#ifdef USE_SHUTTER
+ if (ShutterState(device)) {
+ AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
+ ShutterSetPosition(device, bri * 100.0f );
+ } else
+#endif
+ if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
+ if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
+ if (g_gotct) {
+ light_controller.changeCTB(ct, bri);
+ } else {
+ light_controller.changeHSB(hue, sat, bri);
+ }
+ LightPreparePower();
+ } else { // SetOption68 On, each channel is a dimmer
+ LightSetBri(device, bri);
+ }
+ if (LST_COLDWARM <= local_light_subtype) {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
+ } else {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
+ }
+ XdrvRulesProcess();
+ }
+ change = false;
+ }
+ response += "]";
+ if (2 == response.length()) {
+ response = msg[HUE_ERROR_JSON];
+ }
+ }
+ else {
+ response = msg[HUE_ERROR_JSON];
+ }
+ free(buf);
+}
+#endif // USE_LIGHT
+
+void HueLights(String *path)
+{
+/*
+ * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+ String response;
+ int code = 200;
+ uint8_t device = 1;
+ uint32_t device_id; // the raw device_id used by Hue emulation
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+
+ path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
+ if (path->endsWith(F("/lights"))) { // Got /lights
+ response = F("{");
+ bool appending = false;
+#ifdef USE_LIGHT
+ CheckHue(&response, appending);
+#endif // USE_LIGHT
+#ifdef USE_ZIGBEE
+ ZigbeeCheckHue(&response, appending);
+#endif // USE_ZIGBEE
+#ifdef USE_SCRIPT_HUE
+ Script_Check_Hue(&response);
+#endif
+ response += F("}");
+ }
+ else if (path->endsWith(F("/state"))) { // Got ID/state
+ path->remove(0,8); // Remove /lights/
+ path->remove(path->indexOf(F("/state"))); // Remove /state
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ return ZigbeeHandleHue(shortaddr, device_id, response);
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ return Script_Handle_Hue(path);
+ }
+#endif
+#ifdef USE_LIGHT
+ if ((device >= 1) || (device <= maxhue)) {
+ HueLightsCommand(device, device_id, response);
+ }
+#endif // USE_LIGHT
+
+ }
+ else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
+ path->remove(0,8); // Remove /lights/
+ device_id = atoi(path->c_str());
+ device = DecodeLightId(device_id);
+#ifdef USE_ZIGBEE
+ uint16_t shortaddr;
+ device = DecodeLightId(device_id, &shortaddr);
+ if (shortaddr) {
+ ZigbeeHueStatus(&response, shortaddr);
+ goto exit;
+ }
+#endif // USE_ZIGBEE
+
+#ifdef USE_SCRIPT_HUE
+ if (device > TasmotaGlobal.devices_present) {
+ Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
+ goto exit;
+ }
+#endif
+
+#ifdef USE_LIGHT
+ if ((device < 1) || (device > maxhue)) {
+ device = 1;
+ }
+ response += F("{\"state\":");
+ HueLightStatus1(device, &response);
+ HueLightStatus2(device, &response);
+#endif // USE_LIGHT
+ }
+ else {
+ response = F("{}");
+ code = 406;
+ }
+ exit:
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
+ WSSend(code, CT_APP_JSON, response);
+}
+
+void HueGroups(String *path)
+{
+/*
+ * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
+ */
+ String response(F("{}"));
+ uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
+ //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
+
+ if (path->endsWith(F("/0"))) {
+ UnishoxStrings msg(HUE_LIGHTS);
+ response = msg[HUE_GROUP0_STATUS_JSON];
+ String lights = F("\"1\"");
+ for (uint32_t i = 2; i <= maxhue; i++) {
+ lights += F(",\"");
+ lights += EncodeLightId(i);
+ lights += F("\"");
+ }
+
+#ifdef USE_ZIGBEE
+ ZigbeeHueGroups(&response);
+#endif // USE_ZIGBEE
+ response.replace(F("{l1"), lights);
+#ifdef USE_LIGHT
+ HueLightStatus1(1, &response);
+#endif // USE_LIGHT
+ response += F("}");
+ }
+
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
+ WSSend(200, CT_APP_JSON, response);
+}
+
+void HandleHueApi(String *path)
+{
+ /* HUE API uses /api// syntax. The userid is created by the echo device and
+ * on original HUE the pressed button allows for creation of this user. We simply ignore the
+ * user part and allow every caller as with Web or WeMo.
+ *
+ * (c) Heiko Krupp, 2017
+ *
+ * Hue URL
+ * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ * is converted by webserver to
+ * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
+ */
+
+ uint8_t args = 0;
+
+ path->remove(0, 4); // remove /api
+ uint16_t apilen = path->length();
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
+ for (args = 0; args < Webserver->args(); args++) {
+ String json = Webserver->arg(args);
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
+ }
+
+ UnishoxStrings msg(HUE_API);
+ if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
+ else if (!apilen) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
+ else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
+ else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
+ else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
+ else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
+ else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
+ else HueGlobalConfig(path);
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv20(uint8_t function)
+{
+ bool result = false;
+
+#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
+ if ((EMUL_HUE == Settings.flag2.emulation)) {
+#else
+ if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
+#endif
+ switch (function) {
+ case FUNC_WEB_ADD_HANDLER:
+ WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
+ break;
+ }
+ }
+ return result;
+}
+
+#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino
index 37c3308d0..ac48628df 100644
--- a/tasmota/xdrv_20_hue.ino
+++ b/tasmota/xdrv_20_hue.ino
@@ -411,9 +411,10 @@ String GetHueDeviceId(uint16_t id)
{
String deviceid = WiFi.macAddress();
deviceid += F(":00:11-");
+ if(id<9) deviceid += F("0");
deviceid += String(id);
deviceid.toLowerCase();
- return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
+ return deviceid; // 5c:cf:7f:13:9f:3d:00:11-01
}
String GetHueUserId(void)
From 11127514b08048ed6ca238e310e94d3f97d0e10d Mon Sep 17 00:00:00 2001
From: Stephan Hadinger
Date: Sun, 21 Mar 2021 10:03:58 +0100
Subject: [PATCH 14/35] Berry add wire read/write_bytes
---
.../Berry-0.1.10/src/port/be_wirelib.c | 6 +--
tasmota/xdrv_52_3_berry_wire.ino | 42 +------------------
tasmota/xdrv_52_7_berry_embedded.ino | 24 +++++++++++
3 files changed, 26 insertions(+), 46 deletions(-)
diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
index b6a81c469..fc785c488 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
+++ b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c
@@ -20,8 +20,6 @@ extern int b_wire_scan(bvm *vm);
extern int b_wire_validwrite(bvm *vm);
extern int b_wire_validread(bvm *vm);
-extern int b_wire_readbytes(bvm *vm);
-extern int b_wire_writebytes(bvm *vm);
extern int b_wire_detect(bvm *vm);
// #if !BE_USE_PRECOMPILED_OBJECT
@@ -40,13 +38,11 @@ void be_load_wirelib(bvm *vm)
{ "scan", b_wire_scan },
{ "write", b_wire_validwrite },
{ "read", b_wire_validread },
- { "read_bytes", b_wire_validread },
- { "write_bytes", b_wire_validread },
{ "detect", b_wire_detect },
{ NULL, NULL }
};
- be_regclass(vm, "Wire", members);
+ be_regclass(vm, "Wire_ntv", members);
}
#else
/* @const_object_info_begin
diff --git a/tasmota/xdrv_52_3_berry_wire.ino b/tasmota/xdrv_52_3_berry_wire.ino
index 70e457ca9..1c5c56473 100644
--- a/tasmota/xdrv_52_3_berry_wire.ino
+++ b/tasmota/xdrv_52_3_berry_wire.ino
@@ -138,7 +138,7 @@ extern "C" {
const void * buf;
size_t len;
TwoWire & myWire = getWire(vm);
- if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2))) {
+ if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2) || be_isinstance(vm, 2))) {
if (be_isint(vm, 2)) {
int32_t value = be_toint(vm, 2);
myWire.write(value);
@@ -226,46 +226,6 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
- // Berry: `read_bytes(address:int, reg:int, size:int) -> bytes() or nil`
- int32_t b_wire_readbytes(struct bvm *vm);
- int32_t b_wire_readbytes(struct bvm *vm) {
- // int32_t top = be_top(vm); // Get the number of arguments
- // int32_t bus = getBus(vm);
- // if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
- // uint8_t addr = be_toint(vm, 2);
- // uint8_t reg = be_toint(vm, 3);
- // uint8_t size = be_toint(vm, 4);
- // bool ok = I2cValidRead(addr, reg, size, bus); // TODO
- // if (ok) {
- // be_pushint(vm, i2c_buffer);
- // } else {
- // be_pushnil(vm);
- // }
- // be_return(vm); // Return
- // }
- be_raise(vm, kTypeError, nullptr);
- }
-
- // Berry: `write_bytes(address:int, reg:int, val:bytes()) -> bool or nil`
- int32_t b_wire_writebytes(struct bvm *vm);
- int32_t b_wire_writebytes(struct bvm *vm) {
- // int32_t top = be_top(vm); // Get the number of arguments
- // int32_t bus = getBus(vm);
- // if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
- // uint8_t addr = be_toint(vm, 2);
- // uint8_t reg = be_toint(vm, 3);
- // uint8_t size = be_toint(vm, 4);
- // bool ok = I2cValidRead(addr, reg, size, bus); // TODO
- // if (ok) {
- // be_pushint(vm, i2c_buffer);
- // } else {
- // be_pushnil(vm);
- // }
- // be_return(vm); // Return
- // }
- be_raise(vm, kTypeError, nullptr);
- }
-
// Berry: `find(address:int) -> bool` true if device responds
int32_t b_wire_detect(struct bvm *vm);
int32_t b_wire_detect(struct bvm *vm) {
diff --git a/tasmota/xdrv_52_7_berry_embedded.ino b/tasmota/xdrv_52_7_berry_embedded.ino
index 65dfb616f..3c7834149 100644
--- a/tasmota/xdrv_52_7_berry_embedded.ino
+++ b/tasmota/xdrv_52_7_berry_embedded.ino
@@ -256,6 +256,30 @@ const char berry_prog[] =
// Instantiate tasmota object
"tasmota = Tasmota() "
+
+ // Wire class
+ "class Wire : Wire_ntv "
+ // read bytes as `bytes()` object
+ "def read_bytes(addr,reg,size) "
+ "self._begin_transmission(addr) "
+ "self._write(reg) "
+ "self._end_transmission(false) "
+ "self._request_from(addr,size) "
+ "var ret=bytes(size) "
+ "while (self._available()) "
+ "ret..self._read() "
+ "end "
+ "return ret "
+ "end "
+ // write bytes from `bytes` object
+ "def write_bytes(addr,reg,b) "
+ "self._begin_transmission(addr) "
+ "self._write(reg) "
+ "self._write(b) "
+ "self._end_transmission() "
+ "end "
+ "end "
+
"wire = Wire(0) "
"wire1 = wire "
"wire2 = Wire(1) "
From 9dfd1d1609a65b1bfc4039b83fcb22316ce2cce2 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 21 Mar 2021 12:24:43 +0100
Subject: [PATCH 15/35] Fix CSE7761 read CRC errors
---
tasmota/xnrg_19_cse7761.ino | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/tasmota/xnrg_19_cse7761.ino b/tasmota/xnrg_19_cse7761.ino
index 71594969f..8c7b175a4 100644
--- a/tasmota/xnrg_19_cse7761.ino
+++ b/tasmota/xnrg_19_cse7761.ino
@@ -114,7 +114,7 @@ void Cse7761Write(uint32_t reg, uint32_t data) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: Tx %*_H"), len, buffer);
}
-uint32_t Cse7761Read(uint32_t reg, uint32_t size) {
+uint32_t Cse7761ReadOnce(uint32_t log_level, uint32_t reg, uint32_t size) {
while (Cse7761Serial->available()) { Cse7761Serial->read(); }
Cse7761Write(reg, 0);
@@ -123,8 +123,8 @@ uint32_t Cse7761Read(uint32_t reg, uint32_t size) {
uint32_t rcvd = 0;
uint32_t timeout = millis() + 3;
-// while (!TimeReached(timeout) && (rcvd <= size)) {
- while (!TimeReached(timeout)) {
+ while (!TimeReached(timeout) && (rcvd <= size)) {
+// while (!TimeReached(timeout)) {
int value = Cse7761Serial->read();
if ((value > -1) && (rcvd < sizeof(buffer) -1)) {
buffer[rcvd++] = value;
@@ -150,13 +150,23 @@ uint32_t Cse7761Read(uint32_t reg, uint32_t size) {
}
crc = ~crc;
if (crc != buffer[rcvd]) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("C61: Rx %*_H, CRC error %02X"), rcvd +1, buffer, crc);
+ AddLog(log_level, PSTR("C61: Rx %*_H, CRC error %02X"), rcvd +1, buffer, crc);
return 1;
}
return result;
}
+uint32_t Cse7761Read(uint32_t reg, uint32_t size) {
+ uint32_t value = 0;
+ uint32_t retry = 3;
+ while ((value < 2) && (retry)) {
+ retry--;
+ value = Cse7761ReadOnce((retry) ? LOG_LEVEL_DEBUG_MORE : LOG_LEVEL_DEBUG, reg, size);
+ }
+ return value;
+}
+
uint32_t Cse7761ReadFallback(uint32_t reg, uint32_t prev, uint32_t size) {
uint32_t value = Cse7761Read(reg, size);
if (1 == value) { // CRC Error so use previous value read
@@ -200,8 +210,8 @@ bool Cse7761ChipInit(void) {
Cse7761Write(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
// delay(8); // Exception on ESP8266
- uint32_t timeout = millis() + 8;
- while (!TimeReached(timeout)) { }
+// uint32_t timeout = millis() + 8;
+// while (!TimeReached(timeout)) { }
uint8_t sys_status = Cse7761Read(CSE7761_REG_SYSSTATUS, 1);
#ifdef CSE7761_SIMULATE
From 1f014a1f02d8805c00c3f3cab843832f6c93b266 Mon Sep 17 00:00:00 2001
From: Jason2866 <24528715+Jason2866@users.noreply.github.com>
Date: Sun, 21 Mar 2021 12:51:27 +0100
Subject: [PATCH 16/35] Use Berry as default for Core2 and Odroid-go
---
tasmota/tasmota_configurations_ESP32.h | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h
index 75cfc5572..951a69c4c 100644
--- a/tasmota/tasmota_configurations_ESP32.h
+++ b/tasmota/tasmota_configurations_ESP32.h
@@ -55,6 +55,10 @@
#define USE_UFILESYS
#define USE_SDCARD
#define GUI_TRASH_FILE
+
+#define USE_BERRY // Enable Berry scripting language
+ #define USE_BERRY_PSRAM // Allocate Berry memory in PSRAM if PSRAM is connected - this might be slightly slower but leaves main memory intact
+
#define USE_ADC
#define USE_SPI
#define USE_DISPLAY // Add SPI Display Support (+2k code)
@@ -84,6 +88,10 @@
#define USE_UFILESYS
#define USE_SDCARD
#define GUI_TRASH_FILE
+
+#define USE_BERRY // Enable Berry scripting language
+ #define USE_BERRY_PSRAM // Allocate Berry memory in PSRAM if PSRAM is connected - this might be slightly slower but leaves main memory intact
+
#define USE_I2C
#define USE_BMA423
#define USE_MPU6886
From 9d57411d4d8be2a96a5964652b7e4bfb0d8c2d83 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 21 Mar 2021 14:12:36 +0100
Subject: [PATCH 17/35] Refactor DisplayDimmer
---
tasmota/xdrv_13_display.ino | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index cb9e29936..a8f50d735 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -1643,7 +1643,7 @@ void CmndDisplay(void)
D_CMND_DISP_MODE "\":%d,\"" D_CMND_DISP_DIMMER "\":%d,\"" D_CMND_DISP_SIZE "\":%d,\"" D_CMND_DISP_FONT "\":%d,\""
D_CMND_DISP_ROTATE "\":%d,\"" D_CMND_DISP_REFRESH "\":%d,\"" D_CMND_DISP_COLS "\":[%d,%d],\"" D_CMND_DISP_ROWS "\":%d}}"),
Settings.display_model, Settings.display_width, Settings.display_height,
- Settings.display_mode, ((Settings.display_dimmer * 666) / 100) +1, Settings.display_size, Settings.display_font,
+ Settings.display_mode, changeUIntScale(Settings.display_dimmer, 0, 15, 0, 100), Settings.display_size, Settings.display_font,
Settings.display_rotate, Settings.display_refresh, Settings.display_cols[0], Settings.display_cols[1], Settings.display_rows);
}
@@ -1716,7 +1716,7 @@ void CmndDisplayMode(void)
void CmndDisplayDimmer(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
- Settings.display_dimmer = ((XdrvMailbox.payload +1) * 100) / 666; // Correction for Domoticz (0 - 15)
+ Settings.display_dimmer = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 15); // Correction for Domoticz (0 - 15)
if (Settings.display_dimmer && !(disp_power)) {
ExecuteCommandPower(disp_device, POWER_ON, SRC_DISPLAY);
}
@@ -1729,7 +1729,7 @@ void CmndDisplayDimmer(void) {
XdspCall(FUNC_DISPLAY_DIM);
}
}
- ResponseCmndNumber(((Settings.display_dimmer * 666) / 100) +1);
+ ResponseCmndNumber(changeUIntScale(Settings.display_dimmer, 0, 15, 0, 100));
}
void CmndDisplayBlinkrate(void)
From 9efeceafd930a052471f1374e674d3f815a6fe12 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 21 Mar 2021 14:23:02 +0100
Subject: [PATCH 18/35] Remove text "Module" from GUI main page
---
tasmota/xdrv_01_webserver.ino | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino
index 4595f50a2..7ecf647b4 100644
--- a/tasmota/xdrv_01_webserver.ino
+++ b/tasmota/xdrv_01_webserver.ino
@@ -214,12 +214,15 @@ const char HTTP_HEAD_STYLE3[] PROGMEM =
"" D_MINIMAL_FIRMWARE_PLEASE_UPGRADE "
" // COLOR_TEXT_WARNING
#endif
"" // COLOR_TITLE
+/*
#ifdef LANGUAGE_MODULE_NAME
"
" D_MODULE " %s
"
#else
"
%s " D_MODULE "
"
#endif
- "
%s
";
+*/
+ "
%s
" // Module name
+ "
%s
"; // Device name
const char HTTP_MSG_SLIDER_GRADIENT[] PROGMEM =
"
"
From 8bc48d37b2c2cb6dc6cd3ffb30799ab11c36b6f7 Mon Sep 17 00:00:00 2001
From: Jason2866 <24528715+Jason2866@users.noreply.github.com>
Date: Sun, 21 Mar 2021 14:33:12 +0100
Subject: [PATCH 19/35] No Discovery
---
BUILDS.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/BUILDS.md b/BUILDS.md
index f510ee046..7c8618e39 100644
--- a/BUILDS.md
+++ b/BUILDS.md
@@ -18,9 +18,9 @@
| USE_WEBSEND_RESPONSE | - | - | - | - | - | - | - |
| USE_EMULATION_HUE | - | x | x | - | x | - | - |
| USE_EMULATION_WEMO | - | x | x | - | x | - | - |
-| USE_DISCOVERY | - | - | x | x | - | - | x |
+| USE_DISCOVERY | - | - | - | - | - | - | - |
| WEBSERVER_ADVERTISE | - | - | x | x | - | - | x |
-| MQTT_HOST_DISCOVERY | - | - | x | x | - | - | x |
+| MQTT_HOST_DISCOVERY | - | - | - | - | - | - | - |
| USE_TIMERS | - | x | x | x | x | x | x |
| USE_TIMERS_WEB | - | x | x | x | x | x | x |
| USE_SUNRISE | - | x | x | x | x | x | x |
From 641df673624286ac6fb02a8e8af66c63cf41f9c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?=
Date: Sun, 21 Mar 2021 12:00:00 +0100
Subject: [PATCH 20/35] only perform interlock delay once
Before the delay was executed for *each* device being turned off.
Therefore the delay grew with the size of the interlock group.
---
tasmota/support_tasmota.ino | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino
index 9ad44d32e..94d18f9dc 100644
--- a/tasmota/support_tasmota.ino
+++ b/tasmota/support_tasmota.ino
@@ -556,18 +556,22 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source)
((POWER_ON == state) || ((POWER_TOGGLE == state) && !(TasmotaGlobal.power & mask)))
) {
interlock_mutex = true; // Clear all but masked relay in interlock group if new set requested
+ bool perform_interlock_delay = false;
for (uint32_t i = 0; i < MAX_INTERLOCKS; i++) {
if (Settings.interlock[i] & mask) { // Find interlock group
for (uint32_t j = 0; j < TasmotaGlobal.devices_present; j++) {
power_t imask = 1 << j;
if ((Settings.interlock[i] & imask) && (TasmotaGlobal.power & imask) && (mask != imask)) {
ExecuteCommandPower(j +1, POWER_OFF, SRC_IGNORE);
- delay(50); // Add some delay to make sure never have more than one relay on
+ perform_interlock_delay = true;
}
}
break; // An interlocked relay is only present in one group so quit
}
}
+ if (perform_interlock_delay) {
+ delay(50); // Add some delay to make sure never have more than one relay on
+ }
interlock_mutex = false;
}
From 68fe38d9ec498233a028a15945ef315562cfc83d Mon Sep 17 00:00:00 2001
From: oponyx <78806035+oponyx@users.noreply.github.com>
Date: Sun, 21 Mar 2021 15:49:07 +0100
Subject: [PATCH 21/35] Delete xdrv_20_hue_20210321092519.ino
---
.../tasmota/xdrv_20_hue_20210321092519.ino | 1097 -----------------
1 file changed, 1097 deletions(-)
delete mode 100644 .history/tasmota/xdrv_20_hue_20210321092519.ino
diff --git a/.history/tasmota/xdrv_20_hue_20210321092519.ino b/.history/tasmota/xdrv_20_hue_20210321092519.ino
deleted file mode 100644
index ac48628df..000000000
--- a/.history/tasmota/xdrv_20_hue_20210321092519.ino
+++ /dev/null
@@ -1,1097 +0,0 @@
-/*
- xdrv_20_hue.ino - Philips Hue support for Tasmota
-
- Copyright (C) 2021 Heiko Krupp and Theo Arends
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
-/*********************************************************************************************\
- * Philips Hue bridge emulation
- *
- * Hue Bridge UPNP support routines
- * Need to send 3 response packets with varying ST and USN
- *
- * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
- * Philips Lighting is 00:17:88:00:00:00
-\*********************************************************************************************/
-
-#define XDRV_20 20
-
-#include "UnishoxStrings.h"
-
-const char HUE_RESP_MSG_U[] PROGMEM =
- //=HUE_RESP_RESPONSE
- "HTTP/1.1 200 OK\r\n"
- "HOST: 239.255.255.250:1900\r\n"
- "CACHE-CONTROL: max-age=100\r\n"
- "EXT:\r\n"
- "LOCATION: http://%s:80/description.xml\r\n"
- "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
- "hue-bridgeid: %s\r\n"
- "\0"
- //=HUE_RESP_ST1
- "ST: upnp:rootdevice\r\n"
- "USN: uuid:%s::upnp:rootdevice\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST2
- "ST: uuid:%s\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST3
- "ST: urn:schemas-upnp-org:device:basic:1\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0";
-
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_RESP_RESPONSE=0,
- HUE_RESP_ST1=185,
- HUE_RESP_ST2=240,
- HUE_RESP_ST3=270,
-};
-
-// Compressed from 328 to 247, -24.7%
-const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
- "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
- "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
- "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
- "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
- "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
- "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
- "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
- "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
- "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
- "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
- "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
- "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
-
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-// const char HUE_RESPONSE[] PROGMEM =
-// "HTTP/1.1 200 OK\r\n"
-// "HOST: 239.255.255.250:1900\r\n"
-// "CACHE-CONTROL: max-age=100\r\n"
-// "EXT:\r\n"
-// "LOCATION: http://%s:80/description.xml\r\n"
-// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
-// "hue-bridgeid: %s\r\n";
-// const char HUE_ST1[] PROGMEM =
-// "ST: upnp:rootdevice\r\n"
-// "USN: uuid:%s::upnp:rootdevice\r\n"
-// "\r\n";
-// const char HUE_ST2[] PROGMEM =
-// "ST: uuid:%s\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-// const char HUE_ST3[] PROGMEM =
-// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-
-const char HUE_API_U[] PROGMEM =
- //=HUE_INVALID
- "/invalid/"
- "\0"
- //=HUE_ROOT
- "/"
- "\0"
- //=HUE_CONFIG
- "/config"
- "\0"
- //=HUE_LIGHTS_API
- "/lights"
- "\0"
- //=HUE_GROUPS
- "/groups"
- "\0"
- //=HUE_SCHEDULES
- "/schedules"
- "\0"
- //=HUE_SENSORS
- "/sensors"
- "\0"
- //=HUE_SCENES
- "/scenes"
- "\0"
- //=HUE_RULES
- "/rules"
- "\0"
- //=HUE_RESOURCELINKS
- "/resourcelinks"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_INVALID=0,
- HUE_ROOT=10,
- HUE_CONFIG=12,
- HUE_LIGHTS_API=20,
- HUE_GROUPS=28,
- HUE_SCHEDULES=36,
- HUE_SENSORS=47,
- HUE_SCENES=56,
- HUE_RULES=64,
- HUE_RESOURCELINKS=71,
-};
-
-// Compressed from 86 to 74, -14.0%
-const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
- "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
- "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
- "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-String HueBridgeId(void)
-{
- String temp = WiFi.macAddress();
- temp.replace(":", "");
- String bridgeid = temp.substring(0, 6);
- bridgeid += F("FFFE");
- bridgeid += temp.substring(6);
- return bridgeid; // 5CCF7FFFFE139F3D
-}
-
-String HueSerialnumber(void)
-{
- String serial = WiFi.macAddress();
- serial.replace(":", "");
- serial.toLowerCase();
- return serial; // 5ccf7f139f3d
-}
-
-String HueUuid(void)
-{
- String uuid = F("f6543a06-da50-11ba-8d8f-");
- uuid += HueSerialnumber();
- return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
-}
-
-void HueRespondToMSearch(void)
-{
- char message[TOPSZ];
-
- if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
- UnishoxStrings msg(HUE_RESP_MSG);
- char response[320];
- snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
- int len = strlen(response);
- String uuid = HueUuid();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
- } else {
- snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
- }
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
- message, udp_remote_ip.toString().c_str(), udp_remote_port);
-}
-
-/*********************************************************************************************\
- * Hue web server additions
-\*********************************************************************************************/
-
-//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
-//Successfully compressed from 625 to 391 bytes (-37.4%)
-const size_t HUE_DESCRIPTION_XML_SIZE = 625;
-const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
- "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
- "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
- "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
- "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
- "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
- "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
- "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
- "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
- "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
- "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
- "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
- "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
- "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
- "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
- "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
- "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
- "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
- "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
- "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
-// const char HUE_DESCRIPTION_XML[] PROGMEM =
-// ""
-// ""
-// ""
-// "1"
-// "0"
-// ""
-// "http://{x1:80/"
-// ""
-// "urn:schemas-upnp-org:device:Basic:1"
-// "Amazon-Echo-HA-Bridge ({x1)"
-// "Royal Philips Electronics"
-// "http://www.philips.com"
-// "Philips hue Personal Wireless Lighting"
-// "Philips hue bridge 2012"
-// "929000226503"
-// "{x3"
-// "uuid:{x2"
-// ""
-// "\r\n"
-// "\r\n";
-
-const char HUE_LIGHTS_U[] PROGMEM =
- //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
- "%s\"alert\":\"none\","
- "\"effect\":\"none\","
- "\"reachable\":true}"
- "\0"
- //=HUE_LIGHTS_STATUS_JSON2
- ",\"type\":\"Extended color light\","
- "\"name\":\"%s\","
- "\"modelid\":\"%s\","
- "\"manufacturername\":\"%s\","
- "\"uniqueid\":\"%s\"}"
- "\0"
- //=HUE_GROUP0_STATUS_JSON
- "{\"name\":\"Group 0\","
- "\"lights\":[{l1],"
- "\"type\":\"LightGroup\","
- "\"action\":"
- "\0"
- //=HUE_ERROR_JSON
- "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
- "\0"
- //=HUE_RESP_ON
- "{\"success\":{\"/lights/%d/state/on\":%s}}"
- "\0"
- //=HUE_RESP_NUM
- "{\"success\":{\"/lights/%d/state/%s\":%d}}"
- "\0"
- //=HUE_RESP_XY
- "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
- HUE_LIGHTS_STATUS_JSON2=51,
- HUE_GROUP0_STATUS_JSON=150,
- HUE_ERROR_JSON=213,
- HUE_RESP_ON=283,
- HUE_RESP_NUM=322,
- HUE_RESP_XY=361,
-};
-
-// Compressed from 405 to 275, -32.1%
-const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
- "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
- "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
- "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
- "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
- "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
- "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
- "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
- "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
- "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
- "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
- "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
- "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
- "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-
-
-// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
-// "%s\"alert\":\"none\","
-// "\"effect\":\"none\","
-// "\"reachable\":true}";
-
-// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
-// ",\"type\":\"Extended color light\","
-// "\"name\":\"%s\","
-// "\"modelid\":\"%s\","
-// "\"manufacturername\":\"%s\","
-// "\"uniqueid\":\"%s\"}";
-
-// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
-// "{\"name\":\"Group 0\","
-// "\"lights\":[{l1],"
-// "\"type\":\"LightGroup\","
-// "\"action\":";
-
-// const char HueConfigResponse_JSON[] PROGMEM =
-// "{\"name\":\"Philips hue\","
-// "\"mac\":\"{ma\","
-// "\"dhcp\":true,"
-// "\"ipaddress\":\"{ip\","
-// "\"netmask\":\"{ms\","
-// "\"gateway\":\"{gw\","
-// "\"proxyaddress\":\"none\","
-// "\"proxyport\":0,"
-// "\"bridgeid\":\"{br\","
-// "\"UTC\":\"{dt\","
-// "\"whitelist\":{\"{id\":{"
-// "\"last use date\":\"{dt\","
-// "\"create date\":\"{dt\","
-// "\"name\":\"Remote\"}},"
-// "\"swversion\":\"01041302\","
-// "\"apiversion\":\"1.17.0\","
-// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
-// "\"linkbutton\":false,"
-// "\"portalservices\":false"
-// "}";
-//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
-const size_t HueConfigResponse_JSON_SIZE = 392;
-const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
- "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
- "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
- "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
- "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
- "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
- "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
- "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
- "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
- "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
- "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
- "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
- "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
- "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
- "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
- "\x67\xB8";
-
-// const char HUE_ERROR_JSON[] PROGMEM =
-// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
-
-/********************************************************************************************/
-
-String GetHueDeviceId(uint16_t id)
-{
- String deviceid = WiFi.macAddress();
- deviceid += F(":00:11-");
- if(id<9) deviceid += F("0");
- deviceid += String(id);
- deviceid.toLowerCase();
- return deviceid; // 5c:cf:7f:13:9f:3d:00:11-01
-}
-
-String GetHueUserId(void)
-{
- char userid[7];
-
- snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
- return String(userid);
-}
-
-void HandleUpnpSetupHue(void)
-{
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
- String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
- description_xml.replace(F("{x1"), WiFi.localIP().toString());
- description_xml.replace(F("{x2"), HueUuid());
- description_xml.replace(F("{x3"), HueSerialnumber());
- WSSend(200, CT_XML, description_xml);
-}
-
-void HueNotImplemented(String *path)
-{
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
-
- WSSend(200, CT_APP_JSON, PSTR("{}"));
-}
-
-void HueConfigResponse(String *response)
-{
- *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
- response->replace(F("{ma"), WiFi.macAddress());
- response->replace(F("{ip"), WiFi.localIP().toString());
- response->replace(F("{ms"), WiFi.subnetMask().toString());
- response->replace(F("{gw"), WiFi.gatewayIP().toString());
- response->replace(F("{br"), HueBridgeId());
- response->replace(F("{dt"), GetDateAndTime(DT_UTC));
- response->replace(F("{id"), GetHueUserId());
-}
-
-void HueConfig(String *path)
-{
- String response = "";
- HueConfigResponse(&response);
- WSSend(200, CT_APP_JSON, response);
-}
-
-// device is forced to CT mode instead of HSB
-// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
-bool g_gotct = false;
-
-// store previously set values from the Alexa app
-// it allows to correct slight deviations from value set by the app
-// The Alexa app is very sensitive to exact values
-uint16_t prev_hue = 0;
-uint8_t prev_sat = 0;
-uint8_t prev_bri = 254;
-uint16_t prev_ct = 254;
-char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
-char prev_y_str[24] = "\0";
-
-#ifdef USE_LIGHT
-uint8_t getLocalLightSubtype(uint8_t device) {
- if (TasmotaGlobal.light_type) {
- if (device >= Light.device) {
- if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
- } else {
- return Light.subtype; // the actual light
- }
- } else {
- return LST_NONE; // relays
- }
- } else {
- return LST_NONE;
- }
-}
-
-void HueLightStatus1(uint8_t device, String *response)
-{
- uint16_t ct = 0;
- uint8_t color_mode;
- String light_status = "";
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
- // local_light_subtype simulates the Light.subtype for 'device'
- // For relays LST_NONE, for dimmers LST_SINGLE
- uint8_t local_light_subtype = getLocalLightSubtype(device);
-
- bri = LightGetBri(device); // get Dimmer corrected with SetOption68
- if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
- if (bri < 1) bri = 1;
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
- }
-#endif
-
- if (TasmotaGlobal.light_type) {
- light_state.getHSB(&hue, &sat, nullptr);
- if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
- hue = changeUIntScale(hue, 0, 360, 0, 65535);
-
- if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
- prev_x_str[0] = prev_y_str[0] = 0;
- }
-
- color_mode = light_state.getColorMode();
- ct = light_state.getCT();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- }
-
- const size_t buf_size = 256;
- char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
- UnishoxStrings msg(HUE_LIGHTS);
-
- snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
- // Brightness for all devices with PWM
- if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
- snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
- }
- if (LST_RGB <= local_light_subtype) { // colors
- if (prev_x_str[0] && prev_y_str[0]) {
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
- } else {
- float x, y;
- light_state.getXY(&x, &y);
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
- }
- snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
- }
- if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
- snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
- }
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
-
- *response += buf;
- free(buf);
-}
-
-// Check whether this device should be reported to Alexa or considered hidden.
-// Any device whose friendly name start with "$" is considered hidden
-bool HueActive(uint8_t device) {
- if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
- return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
-}
-
-void HueLightStatus2(uint8_t device, String *response)
-{
- const size_t buf_size = 300;
- char * buf = (char*) malloc(buf_size);
- const size_t max_name_len = 32;
- char fname[max_name_len + 1];
-
- strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
-
- if (device > MAX_FRIENDLYNAMES) {
- uint32_t fname_len = strlen(fname);
- if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
- fname[fname_len++] = '-';
- if (device - MAX_FRIENDLYNAMES < 10) {
- fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
- } else {
- fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
- }
- fname[fname_len] = 0x00;
- }
- UnishoxStrings msg(HUE_LIGHTS);
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
- EscapeJSONString(fname).c_str(),
- EscapeJSONString(Settings.user_template_name).c_str(),
- PSTR("Tasmota"),
- GetHueDeviceId(device).c_str());
- *response += buf;
- free(buf);
-}
-#endif // USE_LIGHT
-
-// generate a unique lightId mixing local IP address and device number
-// it is limited to 32 devices.
-// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
-// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
-#ifndef USE_ZIGBEE
-uint32_t EncodeLightId(uint8_t relay_id)
-#else
-uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
-#endif
-{
- uint8_t mac[6];
- WiFi.macAddress(mac);
- uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
-
- if (relay_id >= 32) { // for Relay #32, we encode as 0
- relay_id = 0;
- }
- if (relay_id > 15) {
- id |= (1 << 28);
- }
- id |= (relay_id & 0xF);
-#ifdef USE_ZIGBEE
- if ((z_shortaddr) && (!relay_id)) {
- // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
- id = (1 << 29) | z_shortaddr;
- }
-#endif
-
- return id;
-}
-
-
-// get hue_id and decode the relay_id
-// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
-// Zigbee:
-// If the Id encodes a Zigbee device (meaning bit 29 is set)
-// it returns 0 and sets the 'shortaddr' to the device short address
-#ifndef USE_ZIGBEE
-uint32_t DecodeLightId(uint32_t hue_id)
-#else
-uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
-#endif
-{
- uint8_t relay_id = hue_id & 0xF;
- if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
- relay_id += 16;
- }
- if (0 == relay_id) { // special value 0 is actually relay #32
- relay_id = 32;
- }
-#ifdef USE_ZIGBEE
- if (shortaddr) { *shortaddr = 0x0000; }
- if (hue_id & (1 << 29)) {
- // this is actually a Zigbee ID
- if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
- relay_id = 0;
- }
-#endif // USE_ZIGBEE
- return relay_id;
-}
-
-// Check if the Echo device is of 1st generation, which triggers different results
-inline uint32_t findEchoGeneration(void) {
- // don't try to guess from User-Agent anymore but use SetOption109
- return Settings.flag4.alexa_gen_1 ? 1 : 2;
-}
-
-void HueGlobalConfig(String *path) {
- String response;
-
- path->remove(0,1); // cut leading / to get
- response = F("{\"lights\":{");
- bool appending = false; // do we need to add a comma to append
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
- response += F("},\"groups\":{},\"schedules\":{},\"config\":");
- HueConfigResponse(&response);
- response += F("}");
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HueAuthentication(String *path)
-{
- char response[38];
-
- snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
- WSSend(200, CT_APP_JSON, response);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
-}
-
-#ifdef USE_LIGHT
-// refactored to remove code duplicates
-void CheckHue(String * response, bool &appending) {
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- for (uint32_t i = 1; i <= maxhue; i++) {
- if (HueActive(i)) {
- if (appending) { *response += ","; }
- *response += F("\"");
- *response += EncodeLightId(i);
- *response += F("\":{\"state\":");
- HueLightStatus1(i, response);
- HueLightStatus2(i, response);
- appending = true;
- }
- }
-}
-
-void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint16_t ct = 0;
- bool on = false;
- bool resp = false; // is the response non null (add comma between parameters)
- bool change = false; // need to change a parameter to the light
- uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
-
- const size_t buf_size = 100;
- char * buf = (char*) malloc(buf_size);
- UnishoxStrings msg(HUE_LIGHTS);
-
- if (Webserver->args()) {
- response = "[";
-
- JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
- JsonParserObject root = parser.getRootObject();
-
- JsonParserToken hue_on = root[PSTR("on")];
- if (hue_on) {
- on = hue_on.getBool();
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_ON],
- device_id, on ? PSTR("true") : PSTR("false"));
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- if (!change) {
- bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
- change = true;
- resp = true;
- response += buf; // actually publish the state
- }
- } else {
-#endif
- ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
- response += buf;
- resp = true;
-#ifdef USE_SHUTTER
- }
-#endif // USE_SHUTTER
- }
-
- if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- light_state.getHSB(&hue, &sat, nullptr);
- bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
- ct = light_state.getCT();
- uint8_t color_mode = light_state.getColorMode();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- // If LCM_BOTH == color_mode, leave g_gotct unchanged
- } else { // treat each channel as simple dimmer
- bri = LightGetBri(device);
- }
- }
- prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
-
- parser.setCurrent();
- JsonParserToken hue_bri = root[PSTR("bri")];
- if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
- bri = hue_bri.getUInt();
- prev_bri = bri; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("bri"), bri);
- response += buf;
- if (LST_SINGLE <= Light.subtype) {
- // extend bri value if set to max
- if (254 <= bri) { bri = 255; }
- change = true;
- }
- resp = true;
- }
-
- // handle xy before Hue/Sat
- // If the request contains both XY and HS, we wan't to give priority to HS
- parser.setCurrent();
- JsonParserToken hue_xy = root[PSTR("xy")];
- if (hue_xy) {
- JsonParserArray arr_xy = JsonParserArray(hue_xy);
- JsonParserToken tok_x = arr_xy[0];
- JsonParserToken tok_y = arr_xy[1];
- float x = tok_x.getFloat();
- float y = tok_y.getFloat();
- strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
- strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
- uint8_t rr,gg,bb;
- XyToRgb(x, y, &rr, &gg, &bb);
- RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
- prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
- prev_sat = (sat > 254 ? 254 : sat);
- //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_XY],
- device_id, prev_x_str, prev_y_str);
- response += buf;
- g_gotct = false;
- resp = true;
- change = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_hue = root[PSTR("hue")];
- if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
- hue = hue_hue.getUInt();
- prev_hue = hue;
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("hue"), hue);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // change range from 0..65535 to 0..360
- hue = changeUIntScale(hue, 0, 65535, 0, 360);
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_sat = root[PSTR("sat")];
- if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
- sat = hue_sat.getUInt();
- prev_sat = sat; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("sat"), sat);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // extend sat value if set to max
- if (254 <= sat) { sat = 255; }
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_ct = root[PSTR("ct")];
- if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
- ct = hue_ct.getUInt();
- prev_ct = ct; // store commande value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("ct"), ct);
- response += buf;
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
- g_gotct = true;
- change = true;
- }
- resp = true;
- }
-
- if (change) {
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
- ShutterSetPosition(device, bri * 100.0f );
- } else
-#endif
- if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- if (g_gotct) {
- light_controller.changeCTB(ct, bri);
- } else {
- light_controller.changeHSB(hue, sat, bri);
- }
- LightPreparePower();
- } else { // SetOption68 On, each channel is a dimmer
- LightSetBri(device, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
- } else {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
- }
- XdrvRulesProcess();
- }
- change = false;
- }
- response += "]";
- if (2 == response.length()) {
- response = msg[HUE_ERROR_JSON];
- }
- }
- else {
- response = msg[HUE_ERROR_JSON];
- }
- free(buf);
-}
-#endif // USE_LIGHT
-
-void HueLights(String *path)
-{
-/*
- * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
- String response;
- int code = 200;
- uint8_t device = 1;
- uint32_t device_id; // the raw device_id used by Hue emulation
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
-
- path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
- if (path->endsWith(F("/lights"))) { // Got /lights
- response = F("{");
- bool appending = false;
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
-#ifdef USE_SCRIPT_HUE
- Script_Check_Hue(&response);
-#endif
- response += F("}");
- }
- else if (path->endsWith(F("/state"))) { // Got ID/state
- path->remove(0,8); // Remove /lights/
- path->remove(path->indexOf(F("/state"))); // Remove /state
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- return ZigbeeHandleHue(shortaddr, device_id, response);
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- return Script_Handle_Hue(path);
- }
-#endif
-#ifdef USE_LIGHT
- if ((device >= 1) || (device <= maxhue)) {
- HueLightsCommand(device, device_id, response);
- }
-#endif // USE_LIGHT
-
- }
- else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
- path->remove(0,8); // Remove /lights/
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- ZigbeeHueStatus(&response, shortaddr);
- goto exit;
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
- goto exit;
- }
-#endif
-
-#ifdef USE_LIGHT
- if ((device < 1) || (device > maxhue)) {
- device = 1;
- }
- response += F("{\"state\":");
- HueLightStatus1(device, &response);
- HueLightStatus2(device, &response);
-#endif // USE_LIGHT
- }
- else {
- response = F("{}");
- code = 406;
- }
- exit:
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
- WSSend(code, CT_APP_JSON, response);
-}
-
-void HueGroups(String *path)
-{
-/*
- * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
- */
- String response(F("{}"));
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
-
- if (path->endsWith(F("/0"))) {
- UnishoxStrings msg(HUE_LIGHTS);
- response = msg[HUE_GROUP0_STATUS_JSON];
- String lights = F("\"1\"");
- for (uint32_t i = 2; i <= maxhue; i++) {
- lights += F(",\"");
- lights += EncodeLightId(i);
- lights += F("\"");
- }
-
-#ifdef USE_ZIGBEE
- ZigbeeHueGroups(&response);
-#endif // USE_ZIGBEE
- response.replace(F("{l1"), lights);
-#ifdef USE_LIGHT
- HueLightStatus1(1, &response);
-#endif // USE_LIGHT
- response += F("}");
- }
-
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HandleHueApi(String *path)
-{
- /* HUE API uses /api// syntax. The userid is created by the echo device and
- * on original HUE the pressed button allows for creation of this user. We simply ignore the
- * user part and allow every caller as with Web or WeMo.
- *
- * (c) Heiko Krupp, 2017
- *
- * Hue URL
- * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- * is converted by webserver to
- * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
-
- uint8_t args = 0;
-
- path->remove(0, 4); // remove /api
- uint16_t apilen = path->length();
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
- for (args = 0; args < Webserver->args(); args++) {
- String json = Webserver->arg(args);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
- }
-
- UnishoxStrings msg(HUE_API);
- if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
- else if (!apilen) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
- else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
- else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
- else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
- else HueGlobalConfig(path);
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-
-bool Xdrv20(uint8_t function)
-{
- bool result = false;
-
-#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
- if ((EMUL_HUE == Settings.flag2.emulation)) {
-#else
- if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
-#endif
- switch (function) {
- case FUNC_WEB_ADD_HANDLER:
- WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
- break;
- }
- }
- return result;
-}
-
-#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
From c64d13ca6631feef1d14a1bfcd0e5370d7c336dd Mon Sep 17 00:00:00 2001
From: oponyx <78806035+oponyx@users.noreply.github.com>
Date: Sun, 21 Mar 2021 15:56:10 +0100
Subject: [PATCH 22/35] Delete .history/tasmota directory
---
.../tasmota/xdrv_20_hue_20210321091627.ino | 1096 ----------------
.../tasmota/xdrv_20_hue_20210321092038.ino | 1097 -----------------
2 files changed, 2193 deletions(-)
delete mode 100644 .history/tasmota/xdrv_20_hue_20210321091627.ino
delete mode 100644 .history/tasmota/xdrv_20_hue_20210321092038.ino
diff --git a/.history/tasmota/xdrv_20_hue_20210321091627.ino b/.history/tasmota/xdrv_20_hue_20210321091627.ino
deleted file mode 100644
index 37c3308d0..000000000
--- a/.history/tasmota/xdrv_20_hue_20210321091627.ino
+++ /dev/null
@@ -1,1096 +0,0 @@
-/*
- xdrv_20_hue.ino - Philips Hue support for Tasmota
-
- Copyright (C) 2021 Heiko Krupp and Theo Arends
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
-/*********************************************************************************************\
- * Philips Hue bridge emulation
- *
- * Hue Bridge UPNP support routines
- * Need to send 3 response packets with varying ST and USN
- *
- * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
- * Philips Lighting is 00:17:88:00:00:00
-\*********************************************************************************************/
-
-#define XDRV_20 20
-
-#include "UnishoxStrings.h"
-
-const char HUE_RESP_MSG_U[] PROGMEM =
- //=HUE_RESP_RESPONSE
- "HTTP/1.1 200 OK\r\n"
- "HOST: 239.255.255.250:1900\r\n"
- "CACHE-CONTROL: max-age=100\r\n"
- "EXT:\r\n"
- "LOCATION: http://%s:80/description.xml\r\n"
- "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
- "hue-bridgeid: %s\r\n"
- "\0"
- //=HUE_RESP_ST1
- "ST: upnp:rootdevice\r\n"
- "USN: uuid:%s::upnp:rootdevice\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST2
- "ST: uuid:%s\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST3
- "ST: urn:schemas-upnp-org:device:basic:1\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0";
-
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_RESP_RESPONSE=0,
- HUE_RESP_ST1=185,
- HUE_RESP_ST2=240,
- HUE_RESP_ST3=270,
-};
-
-// Compressed from 328 to 247, -24.7%
-const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
- "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
- "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
- "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
- "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
- "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
- "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
- "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
- "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
- "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
- "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
- "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
- "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
-
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-// const char HUE_RESPONSE[] PROGMEM =
-// "HTTP/1.1 200 OK\r\n"
-// "HOST: 239.255.255.250:1900\r\n"
-// "CACHE-CONTROL: max-age=100\r\n"
-// "EXT:\r\n"
-// "LOCATION: http://%s:80/description.xml\r\n"
-// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
-// "hue-bridgeid: %s\r\n";
-// const char HUE_ST1[] PROGMEM =
-// "ST: upnp:rootdevice\r\n"
-// "USN: uuid:%s::upnp:rootdevice\r\n"
-// "\r\n";
-// const char HUE_ST2[] PROGMEM =
-// "ST: uuid:%s\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-// const char HUE_ST3[] PROGMEM =
-// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-
-const char HUE_API_U[] PROGMEM =
- //=HUE_INVALID
- "/invalid/"
- "\0"
- //=HUE_ROOT
- "/"
- "\0"
- //=HUE_CONFIG
- "/config"
- "\0"
- //=HUE_LIGHTS_API
- "/lights"
- "\0"
- //=HUE_GROUPS
- "/groups"
- "\0"
- //=HUE_SCHEDULES
- "/schedules"
- "\0"
- //=HUE_SENSORS
- "/sensors"
- "\0"
- //=HUE_SCENES
- "/scenes"
- "\0"
- //=HUE_RULES
- "/rules"
- "\0"
- //=HUE_RESOURCELINKS
- "/resourcelinks"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_INVALID=0,
- HUE_ROOT=10,
- HUE_CONFIG=12,
- HUE_LIGHTS_API=20,
- HUE_GROUPS=28,
- HUE_SCHEDULES=36,
- HUE_SENSORS=47,
- HUE_SCENES=56,
- HUE_RULES=64,
- HUE_RESOURCELINKS=71,
-};
-
-// Compressed from 86 to 74, -14.0%
-const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
- "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
- "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
- "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-String HueBridgeId(void)
-{
- String temp = WiFi.macAddress();
- temp.replace(":", "");
- String bridgeid = temp.substring(0, 6);
- bridgeid += F("FFFE");
- bridgeid += temp.substring(6);
- return bridgeid; // 5CCF7FFFFE139F3D
-}
-
-String HueSerialnumber(void)
-{
- String serial = WiFi.macAddress();
- serial.replace(":", "");
- serial.toLowerCase();
- return serial; // 5ccf7f139f3d
-}
-
-String HueUuid(void)
-{
- String uuid = F("f6543a06-da50-11ba-8d8f-");
- uuid += HueSerialnumber();
- return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
-}
-
-void HueRespondToMSearch(void)
-{
- char message[TOPSZ];
-
- if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
- UnishoxStrings msg(HUE_RESP_MSG);
- char response[320];
- snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
- int len = strlen(response);
- String uuid = HueUuid();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
- } else {
- snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
- }
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
- message, udp_remote_ip.toString().c_str(), udp_remote_port);
-}
-
-/*********************************************************************************************\
- * Hue web server additions
-\*********************************************************************************************/
-
-//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
-//Successfully compressed from 625 to 391 bytes (-37.4%)
-const size_t HUE_DESCRIPTION_XML_SIZE = 625;
-const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
- "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
- "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
- "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
- "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
- "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
- "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
- "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
- "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
- "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
- "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
- "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
- "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
- "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
- "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
- "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
- "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
- "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
- "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
- "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
-// const char HUE_DESCRIPTION_XML[] PROGMEM =
-// ""
-// ""
-// ""
-// "1"
-// "0"
-// ""
-// "http://{x1:80/"
-// ""
-// "urn:schemas-upnp-org:device:Basic:1"
-// "Amazon-Echo-HA-Bridge ({x1)"
-// "Royal Philips Electronics"
-// "http://www.philips.com"
-// "Philips hue Personal Wireless Lighting"
-// "Philips hue bridge 2012"
-// "929000226503"
-// "{x3"
-// "uuid:{x2"
-// ""
-// "\r\n"
-// "\r\n";
-
-const char HUE_LIGHTS_U[] PROGMEM =
- //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
- "%s\"alert\":\"none\","
- "\"effect\":\"none\","
- "\"reachable\":true}"
- "\0"
- //=HUE_LIGHTS_STATUS_JSON2
- ",\"type\":\"Extended color light\","
- "\"name\":\"%s\","
- "\"modelid\":\"%s\","
- "\"manufacturername\":\"%s\","
- "\"uniqueid\":\"%s\"}"
- "\0"
- //=HUE_GROUP0_STATUS_JSON
- "{\"name\":\"Group 0\","
- "\"lights\":[{l1],"
- "\"type\":\"LightGroup\","
- "\"action\":"
- "\0"
- //=HUE_ERROR_JSON
- "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
- "\0"
- //=HUE_RESP_ON
- "{\"success\":{\"/lights/%d/state/on\":%s}}"
- "\0"
- //=HUE_RESP_NUM
- "{\"success\":{\"/lights/%d/state/%s\":%d}}"
- "\0"
- //=HUE_RESP_XY
- "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
- HUE_LIGHTS_STATUS_JSON2=51,
- HUE_GROUP0_STATUS_JSON=150,
- HUE_ERROR_JSON=213,
- HUE_RESP_ON=283,
- HUE_RESP_NUM=322,
- HUE_RESP_XY=361,
-};
-
-// Compressed from 405 to 275, -32.1%
-const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
- "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
- "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
- "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
- "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
- "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
- "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
- "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
- "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
- "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
- "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
- "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
- "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
- "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-
-
-// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
-// "%s\"alert\":\"none\","
-// "\"effect\":\"none\","
-// "\"reachable\":true}";
-
-// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
-// ",\"type\":\"Extended color light\","
-// "\"name\":\"%s\","
-// "\"modelid\":\"%s\","
-// "\"manufacturername\":\"%s\","
-// "\"uniqueid\":\"%s\"}";
-
-// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
-// "{\"name\":\"Group 0\","
-// "\"lights\":[{l1],"
-// "\"type\":\"LightGroup\","
-// "\"action\":";
-
-// const char HueConfigResponse_JSON[] PROGMEM =
-// "{\"name\":\"Philips hue\","
-// "\"mac\":\"{ma\","
-// "\"dhcp\":true,"
-// "\"ipaddress\":\"{ip\","
-// "\"netmask\":\"{ms\","
-// "\"gateway\":\"{gw\","
-// "\"proxyaddress\":\"none\","
-// "\"proxyport\":0,"
-// "\"bridgeid\":\"{br\","
-// "\"UTC\":\"{dt\","
-// "\"whitelist\":{\"{id\":{"
-// "\"last use date\":\"{dt\","
-// "\"create date\":\"{dt\","
-// "\"name\":\"Remote\"}},"
-// "\"swversion\":\"01041302\","
-// "\"apiversion\":\"1.17.0\","
-// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
-// "\"linkbutton\":false,"
-// "\"portalservices\":false"
-// "}";
-//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
-const size_t HueConfigResponse_JSON_SIZE = 392;
-const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
- "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
- "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
- "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
- "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
- "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
- "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
- "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
- "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
- "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
- "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
- "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
- "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
- "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
- "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
- "\x67\xB8";
-
-// const char HUE_ERROR_JSON[] PROGMEM =
-// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
-
-/********************************************************************************************/
-
-String GetHueDeviceId(uint16_t id)
-{
- String deviceid = WiFi.macAddress();
- deviceid += F(":00:11-");
- deviceid += String(id);
- deviceid.toLowerCase();
- return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
-}
-
-String GetHueUserId(void)
-{
- char userid[7];
-
- snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
- return String(userid);
-}
-
-void HandleUpnpSetupHue(void)
-{
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
- String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
- description_xml.replace(F("{x1"), WiFi.localIP().toString());
- description_xml.replace(F("{x2"), HueUuid());
- description_xml.replace(F("{x3"), HueSerialnumber());
- WSSend(200, CT_XML, description_xml);
-}
-
-void HueNotImplemented(String *path)
-{
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
-
- WSSend(200, CT_APP_JSON, PSTR("{}"));
-}
-
-void HueConfigResponse(String *response)
-{
- *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
- response->replace(F("{ma"), WiFi.macAddress());
- response->replace(F("{ip"), WiFi.localIP().toString());
- response->replace(F("{ms"), WiFi.subnetMask().toString());
- response->replace(F("{gw"), WiFi.gatewayIP().toString());
- response->replace(F("{br"), HueBridgeId());
- response->replace(F("{dt"), GetDateAndTime(DT_UTC));
- response->replace(F("{id"), GetHueUserId());
-}
-
-void HueConfig(String *path)
-{
- String response = "";
- HueConfigResponse(&response);
- WSSend(200, CT_APP_JSON, response);
-}
-
-// device is forced to CT mode instead of HSB
-// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
-bool g_gotct = false;
-
-// store previously set values from the Alexa app
-// it allows to correct slight deviations from value set by the app
-// The Alexa app is very sensitive to exact values
-uint16_t prev_hue = 0;
-uint8_t prev_sat = 0;
-uint8_t prev_bri = 254;
-uint16_t prev_ct = 254;
-char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
-char prev_y_str[24] = "\0";
-
-#ifdef USE_LIGHT
-uint8_t getLocalLightSubtype(uint8_t device) {
- if (TasmotaGlobal.light_type) {
- if (device >= Light.device) {
- if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
- } else {
- return Light.subtype; // the actual light
- }
- } else {
- return LST_NONE; // relays
- }
- } else {
- return LST_NONE;
- }
-}
-
-void HueLightStatus1(uint8_t device, String *response)
-{
- uint16_t ct = 0;
- uint8_t color_mode;
- String light_status = "";
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
- // local_light_subtype simulates the Light.subtype for 'device'
- // For relays LST_NONE, for dimmers LST_SINGLE
- uint8_t local_light_subtype = getLocalLightSubtype(device);
-
- bri = LightGetBri(device); // get Dimmer corrected with SetOption68
- if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
- if (bri < 1) bri = 1;
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
- }
-#endif
-
- if (TasmotaGlobal.light_type) {
- light_state.getHSB(&hue, &sat, nullptr);
- if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
- hue = changeUIntScale(hue, 0, 360, 0, 65535);
-
- if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
- prev_x_str[0] = prev_y_str[0] = 0;
- }
-
- color_mode = light_state.getColorMode();
- ct = light_state.getCT();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- }
-
- const size_t buf_size = 256;
- char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
- UnishoxStrings msg(HUE_LIGHTS);
-
- snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
- // Brightness for all devices with PWM
- if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
- snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
- }
- if (LST_RGB <= local_light_subtype) { // colors
- if (prev_x_str[0] && prev_y_str[0]) {
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
- } else {
- float x, y;
- light_state.getXY(&x, &y);
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
- }
- snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
- }
- if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
- snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
- }
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
-
- *response += buf;
- free(buf);
-}
-
-// Check whether this device should be reported to Alexa or considered hidden.
-// Any device whose friendly name start with "$" is considered hidden
-bool HueActive(uint8_t device) {
- if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
- return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
-}
-
-void HueLightStatus2(uint8_t device, String *response)
-{
- const size_t buf_size = 300;
- char * buf = (char*) malloc(buf_size);
- const size_t max_name_len = 32;
- char fname[max_name_len + 1];
-
- strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
-
- if (device > MAX_FRIENDLYNAMES) {
- uint32_t fname_len = strlen(fname);
- if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
- fname[fname_len++] = '-';
- if (device - MAX_FRIENDLYNAMES < 10) {
- fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
- } else {
- fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
- }
- fname[fname_len] = 0x00;
- }
- UnishoxStrings msg(HUE_LIGHTS);
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
- EscapeJSONString(fname).c_str(),
- EscapeJSONString(Settings.user_template_name).c_str(),
- PSTR("Tasmota"),
- GetHueDeviceId(device).c_str());
- *response += buf;
- free(buf);
-}
-#endif // USE_LIGHT
-
-// generate a unique lightId mixing local IP address and device number
-// it is limited to 32 devices.
-// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
-// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
-#ifndef USE_ZIGBEE
-uint32_t EncodeLightId(uint8_t relay_id)
-#else
-uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
-#endif
-{
- uint8_t mac[6];
- WiFi.macAddress(mac);
- uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
-
- if (relay_id >= 32) { // for Relay #32, we encode as 0
- relay_id = 0;
- }
- if (relay_id > 15) {
- id |= (1 << 28);
- }
- id |= (relay_id & 0xF);
-#ifdef USE_ZIGBEE
- if ((z_shortaddr) && (!relay_id)) {
- // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
- id = (1 << 29) | z_shortaddr;
- }
-#endif
-
- return id;
-}
-
-
-// get hue_id and decode the relay_id
-// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
-// Zigbee:
-// If the Id encodes a Zigbee device (meaning bit 29 is set)
-// it returns 0 and sets the 'shortaddr' to the device short address
-#ifndef USE_ZIGBEE
-uint32_t DecodeLightId(uint32_t hue_id)
-#else
-uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
-#endif
-{
- uint8_t relay_id = hue_id & 0xF;
- if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
- relay_id += 16;
- }
- if (0 == relay_id) { // special value 0 is actually relay #32
- relay_id = 32;
- }
-#ifdef USE_ZIGBEE
- if (shortaddr) { *shortaddr = 0x0000; }
- if (hue_id & (1 << 29)) {
- // this is actually a Zigbee ID
- if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
- relay_id = 0;
- }
-#endif // USE_ZIGBEE
- return relay_id;
-}
-
-// Check if the Echo device is of 1st generation, which triggers different results
-inline uint32_t findEchoGeneration(void) {
- // don't try to guess from User-Agent anymore but use SetOption109
- return Settings.flag4.alexa_gen_1 ? 1 : 2;
-}
-
-void HueGlobalConfig(String *path) {
- String response;
-
- path->remove(0,1); // cut leading / to get
- response = F("{\"lights\":{");
- bool appending = false; // do we need to add a comma to append
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
- response += F("},\"groups\":{},\"schedules\":{},\"config\":");
- HueConfigResponse(&response);
- response += F("}");
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HueAuthentication(String *path)
-{
- char response[38];
-
- snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
- WSSend(200, CT_APP_JSON, response);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
-}
-
-#ifdef USE_LIGHT
-// refactored to remove code duplicates
-void CheckHue(String * response, bool &appending) {
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- for (uint32_t i = 1; i <= maxhue; i++) {
- if (HueActive(i)) {
- if (appending) { *response += ","; }
- *response += F("\"");
- *response += EncodeLightId(i);
- *response += F("\":{\"state\":");
- HueLightStatus1(i, response);
- HueLightStatus2(i, response);
- appending = true;
- }
- }
-}
-
-void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint16_t ct = 0;
- bool on = false;
- bool resp = false; // is the response non null (add comma between parameters)
- bool change = false; // need to change a parameter to the light
- uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
-
- const size_t buf_size = 100;
- char * buf = (char*) malloc(buf_size);
- UnishoxStrings msg(HUE_LIGHTS);
-
- if (Webserver->args()) {
- response = "[";
-
- JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
- JsonParserObject root = parser.getRootObject();
-
- JsonParserToken hue_on = root[PSTR("on")];
- if (hue_on) {
- on = hue_on.getBool();
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_ON],
- device_id, on ? PSTR("true") : PSTR("false"));
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- if (!change) {
- bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
- change = true;
- resp = true;
- response += buf; // actually publish the state
- }
- } else {
-#endif
- ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
- response += buf;
- resp = true;
-#ifdef USE_SHUTTER
- }
-#endif // USE_SHUTTER
- }
-
- if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- light_state.getHSB(&hue, &sat, nullptr);
- bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
- ct = light_state.getCT();
- uint8_t color_mode = light_state.getColorMode();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- // If LCM_BOTH == color_mode, leave g_gotct unchanged
- } else { // treat each channel as simple dimmer
- bri = LightGetBri(device);
- }
- }
- prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
-
- parser.setCurrent();
- JsonParserToken hue_bri = root[PSTR("bri")];
- if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
- bri = hue_bri.getUInt();
- prev_bri = bri; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("bri"), bri);
- response += buf;
- if (LST_SINGLE <= Light.subtype) {
- // extend bri value if set to max
- if (254 <= bri) { bri = 255; }
- change = true;
- }
- resp = true;
- }
-
- // handle xy before Hue/Sat
- // If the request contains both XY and HS, we wan't to give priority to HS
- parser.setCurrent();
- JsonParserToken hue_xy = root[PSTR("xy")];
- if (hue_xy) {
- JsonParserArray arr_xy = JsonParserArray(hue_xy);
- JsonParserToken tok_x = arr_xy[0];
- JsonParserToken tok_y = arr_xy[1];
- float x = tok_x.getFloat();
- float y = tok_y.getFloat();
- strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
- strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
- uint8_t rr,gg,bb;
- XyToRgb(x, y, &rr, &gg, &bb);
- RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
- prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
- prev_sat = (sat > 254 ? 254 : sat);
- //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_XY],
- device_id, prev_x_str, prev_y_str);
- response += buf;
- g_gotct = false;
- resp = true;
- change = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_hue = root[PSTR("hue")];
- if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
- hue = hue_hue.getUInt();
- prev_hue = hue;
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("hue"), hue);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // change range from 0..65535 to 0..360
- hue = changeUIntScale(hue, 0, 65535, 0, 360);
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_sat = root[PSTR("sat")];
- if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
- sat = hue_sat.getUInt();
- prev_sat = sat; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("sat"), sat);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // extend sat value if set to max
- if (254 <= sat) { sat = 255; }
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_ct = root[PSTR("ct")];
- if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
- ct = hue_ct.getUInt();
- prev_ct = ct; // store commande value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("ct"), ct);
- response += buf;
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
- g_gotct = true;
- change = true;
- }
- resp = true;
- }
-
- if (change) {
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
- ShutterSetPosition(device, bri * 100.0f );
- } else
-#endif
- if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- if (g_gotct) {
- light_controller.changeCTB(ct, bri);
- } else {
- light_controller.changeHSB(hue, sat, bri);
- }
- LightPreparePower();
- } else { // SetOption68 On, each channel is a dimmer
- LightSetBri(device, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
- } else {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
- }
- XdrvRulesProcess();
- }
- change = false;
- }
- response += "]";
- if (2 == response.length()) {
- response = msg[HUE_ERROR_JSON];
- }
- }
- else {
- response = msg[HUE_ERROR_JSON];
- }
- free(buf);
-}
-#endif // USE_LIGHT
-
-void HueLights(String *path)
-{
-/*
- * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
- String response;
- int code = 200;
- uint8_t device = 1;
- uint32_t device_id; // the raw device_id used by Hue emulation
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
-
- path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
- if (path->endsWith(F("/lights"))) { // Got /lights
- response = F("{");
- bool appending = false;
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
-#ifdef USE_SCRIPT_HUE
- Script_Check_Hue(&response);
-#endif
- response += F("}");
- }
- else if (path->endsWith(F("/state"))) { // Got ID/state
- path->remove(0,8); // Remove /lights/
- path->remove(path->indexOf(F("/state"))); // Remove /state
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- return ZigbeeHandleHue(shortaddr, device_id, response);
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- return Script_Handle_Hue(path);
- }
-#endif
-#ifdef USE_LIGHT
- if ((device >= 1) || (device <= maxhue)) {
- HueLightsCommand(device, device_id, response);
- }
-#endif // USE_LIGHT
-
- }
- else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
- path->remove(0,8); // Remove /lights/
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- ZigbeeHueStatus(&response, shortaddr);
- goto exit;
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
- goto exit;
- }
-#endif
-
-#ifdef USE_LIGHT
- if ((device < 1) || (device > maxhue)) {
- device = 1;
- }
- response += F("{\"state\":");
- HueLightStatus1(device, &response);
- HueLightStatus2(device, &response);
-#endif // USE_LIGHT
- }
- else {
- response = F("{}");
- code = 406;
- }
- exit:
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
- WSSend(code, CT_APP_JSON, response);
-}
-
-void HueGroups(String *path)
-{
-/*
- * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
- */
- String response(F("{}"));
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
-
- if (path->endsWith(F("/0"))) {
- UnishoxStrings msg(HUE_LIGHTS);
- response = msg[HUE_GROUP0_STATUS_JSON];
- String lights = F("\"1\"");
- for (uint32_t i = 2; i <= maxhue; i++) {
- lights += F(",\"");
- lights += EncodeLightId(i);
- lights += F("\"");
- }
-
-#ifdef USE_ZIGBEE
- ZigbeeHueGroups(&response);
-#endif // USE_ZIGBEE
- response.replace(F("{l1"), lights);
-#ifdef USE_LIGHT
- HueLightStatus1(1, &response);
-#endif // USE_LIGHT
- response += F("}");
- }
-
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HandleHueApi(String *path)
-{
- /* HUE API uses /api// syntax. The userid is created by the echo device and
- * on original HUE the pressed button allows for creation of this user. We simply ignore the
- * user part and allow every caller as with Web or WeMo.
- *
- * (c) Heiko Krupp, 2017
- *
- * Hue URL
- * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- * is converted by webserver to
- * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
-
- uint8_t args = 0;
-
- path->remove(0, 4); // remove /api
- uint16_t apilen = path->length();
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
- for (args = 0; args < Webserver->args(); args++) {
- String json = Webserver->arg(args);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
- }
-
- UnishoxStrings msg(HUE_API);
- if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
- else if (!apilen) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
- else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
- else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
- else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
- else HueGlobalConfig(path);
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-
-bool Xdrv20(uint8_t function)
-{
- bool result = false;
-
-#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
- if ((EMUL_HUE == Settings.flag2.emulation)) {
-#else
- if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
-#endif
- switch (function) {
- case FUNC_WEB_ADD_HANDLER:
- WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
- break;
- }
- }
- return result;
-}
-
-#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
diff --git a/.history/tasmota/xdrv_20_hue_20210321092038.ino b/.history/tasmota/xdrv_20_hue_20210321092038.ino
deleted file mode 100644
index 0058087a9..000000000
--- a/.history/tasmota/xdrv_20_hue_20210321092038.ino
+++ /dev/null
@@ -1,1097 +0,0 @@
-/*
- xdrv_20_hue.ino - Philips Hue support for Tasmota
-
- Copyright (C) 2021 Heiko Krupp and Theo Arends
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see .
-*/
-
-#if defined(USE_WEBSERVER) && defined(USE_EMULATION) && defined(USE_EMULATION_HUE) && (defined(USE_ZIGBEE) || defined(USE_LIGHT))
-/*********************************************************************************************\
- * Philips Hue bridge emulation
- *
- * Hue Bridge UPNP support routines
- * Need to send 3 response packets with varying ST and USN
- *
- * Using Espressif Inc Mac Address of 5C:CF:7F:00:00:00
- * Philips Lighting is 00:17:88:00:00:00
-\*********************************************************************************************/
-
-#define XDRV_20 20
-
-#include "UnishoxStrings.h"
-
-const char HUE_RESP_MSG_U[] PROGMEM =
- //=HUE_RESP_RESPONSE
- "HTTP/1.1 200 OK\r\n"
- "HOST: 239.255.255.250:1900\r\n"
- "CACHE-CONTROL: max-age=100\r\n"
- "EXT:\r\n"
- "LOCATION: http://%s:80/description.xml\r\n"
- "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
- "hue-bridgeid: %s\r\n"
- "\0"
- //=HUE_RESP_ST1
- "ST: upnp:rootdevice\r\n"
- "USN: uuid:%s::upnp:rootdevice\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST2
- "ST: uuid:%s\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0"
- //=HUE_RESP_ST3
- "ST: urn:schemas-upnp-org:device:basic:1\r\n"
- "USN: uuid:%s\r\n"
- "\r\n"
- "\0";
-
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_RESP_RESPONSE=0,
- HUE_RESP_ST1=185,
- HUE_RESP_ST2=240,
- HUE_RESP_ST3=270,
-};
-
-// Compressed from 328 to 247, -24.7%
-const char HUE_RESP_MSG[] PROGMEM = "\x00\x15\x21\x45\x45\x44\x30\xEC\x39\x0E\x90\xEE\x53\x67\x70\x8B\x08\xD2\x70\xA4"
- "\x6E\x21\x45\x85\xE2\xA3\xCD\x1C\xAB\x47\x4A\xDD\x3A\x56\xE9\xD2\xB5\x9E\x71\x36"
- "\x53\x85\x23\x71\x06\x56\x41\x90\xA2\x67\x59\x10\x79\xD5\xFC\x08\x8F\x34\x36\xCD"
- "\x87\x5D\x8F\x33\xE1\xC8\xD9\x4E\x14\x8D\xC4\xC8\xD8\x54\x79\xCE\x14\x8D\xC4\x41"
- "\x60\x77\x5B\x9C\x47\x9A\x15\x54\x30\xF3\x3B\x0E\xC3\xEB\xC7\x99\xCF\xB3\xB0\x84"
- "\x7E\x0F\xFA\x32\xB7\x38\xE8\x6C\x1A\x14\xE1\x48\xDC\x45\xE7\xF3\x37\xF2\x3C\xD1"
- "\x05\xBC\x2C\xD8\x76\x1C\xB3\xA4\xC3\xA3\x3B\x84\x42\xC8\x67\x10\xC3\xB0\xE4\x3A"
- "\x33\xB8\x45\xA3\x08\x77\xF4\x41\xE6\x76\x1C\x87\x4A\xC3\xA3\x29\xC2\x91\xB8\x50"
- "\xB6\x75\x8E\xFE\x88\x3C\xF4\x43\xCD\x1F\x5E\x9C\x29\x1B\xA7\x0B\xE5\xE2\xA3\xCD"
- "\x0B\x19\xC3\x0F\x3F\xE6\x50\x8C\xCF\x43\x73\x85\x23\x71\x0B\x2F\x17\x1E\x68\x58"
- "\xBD\x10\xF3\x3E\xBC\x79\x9E\x60\x99\x6C\x10\xF1\x30\x41\xBA\x09\x38\x58\x22\xDA"
- "\xFF\x1E\x7E\x0C\x53\x1B\x7E\x3A\xC5\x8C\xE1\x87\x5E\x7C\x78\xF3\x04\x1C\x78\xF3"
- "\x1D\x7E\xD0\xCF\x33\x90\x81\x3B\x16";
-
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-// const char HUE_RESPONSE[] PROGMEM =
-// "HTTP/1.1 200 OK\r\n"
-// "HOST: 239.255.255.250:1900\r\n"
-// "CACHE-CONTROL: max-age=100\r\n"
-// "EXT:\r\n"
-// "LOCATION: http://%s:80/description.xml\r\n"
-// "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.24.0\r\n" // was 1.17
-// "hue-bridgeid: %s\r\n";
-// const char HUE_ST1[] PROGMEM =
-// "ST: upnp:rootdevice\r\n"
-// "USN: uuid:%s::upnp:rootdevice\r\n"
-// "\r\n";
-// const char HUE_ST2[] PROGMEM =
-// "ST: uuid:%s\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-// const char HUE_ST3[] PROGMEM =
-// "ST: urn:schemas-upnp-org:device:basic:1\r\n"
-// "USN: uuid:%s\r\n"
-// "\r\n";
-
-const char HUE_API_U[] PROGMEM =
- //=HUE_INVALID
- "/invalid/"
- "\0"
- //=HUE_ROOT
- "/"
- "\0"
- //=HUE_CONFIG
- "/config"
- "\0"
- //=HUE_LIGHTS_API
- "/lights"
- "\0"
- //=HUE_GROUPS
- "/groups"
- "\0"
- //=HUE_SCHEDULES
- "/schedules"
- "\0"
- //=HUE_SENSORS
- "/sensors"
- "\0"
- //=HUE_SCENES
- "/scenes"
- "\0"
- //=HUE_RULES
- "/rules"
- "\0"
- //=HUE_RESOURCELINKS
- "/resourcelinks"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_INVALID=0,
- HUE_ROOT=10,
- HUE_CONFIG=12,
- HUE_LIGHTS_API=20,
- HUE_GROUPS=28,
- HUE_SCHEDULES=36,
- HUE_SENSORS=47,
- HUE_SCENES=56,
- HUE_RULES=64,
- HUE_RESOURCELINKS=71,
-};
-
-// Compressed from 86 to 74, -14.0%
-const char HUE_API[] PROGMEM = "\x00\x06\x3B\x37\x8C\xEC\x2D\x10\xEC\x9C\x2F\x9D\x93\x85\xF3\xB0\x3C\xE3\x1A\x3D"
- "\x38\x5F\x3B\x02\xD1\xE1\x55\xE9\xC2\xF9\xD8\x3D\xFC\x16\x33\xD3\x85\xF3\xB3\xC1"
- "\x8A\x62\x0B\x09\xFA\x70\xBE\x76\x79\xF7\xB3\xFE\x9C\x2F\x9D\x9E\x0D\xF3\xF4\xE1"
- "\x7C\xEC\xF8\x20\xD4\xFB\xF6\x0B\xF8\x6C\x2D\xE3\x4F\x4E\x17\xCD";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-String HueBridgeId(void)
-{
- String temp = WiFi.macAddress();
- temp.replace(":", "");
- String bridgeid = temp.substring(0, 6);
- bridgeid += F("FFFE");
- bridgeid += temp.substring(6);
- return bridgeid; // 5CCF7FFFFE139F3D
-}
-
-String HueSerialnumber(void)
-{
- String serial = WiFi.macAddress();
- serial.replace(":", "");
- serial.toLowerCase();
- return serial; // 5ccf7f139f3d
-}
-
-String HueUuid(void)
-{
- String uuid = F("f6543a06-da50-11ba-8d8f-");
- uuid += HueSerialnumber();
- return uuid; // f6543a06-da50-11ba-8d8f-5ccf7f139f3d
-}
-
-void HueRespondToMSearch(void)
-{
- char message[TOPSZ];
-
- if (PortUdp.beginPacket(udp_remote_ip, udp_remote_port)) {
- UnishoxStrings msg(HUE_RESP_MSG);
- char response[320];
- snprintf_P(response, sizeof(response), msg[HUE_RESP_RESPONSE], WiFi.localIP().toString().c_str(), HueBridgeId().c_str());
- int len = strlen(response);
- String uuid = HueUuid();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST1], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST2], uuid.c_str(), uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(response + len, sizeof(response) - len, msg[HUE_RESP_ST3], uuid.c_str());
- PortUdp.write(response);
- PortUdp.endPacket();
-
- snprintf_P(message, sizeof(message), PSTR(D_3_RESPONSE_PACKETS_SENT));
- } else {
- snprintf_P(message, sizeof(message), PSTR(D_FAILED_TO_SEND_RESPONSE));
- }
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"),
- message, udp_remote_ip.toString().c_str(), udp_remote_port);
-}
-
-/*********************************************************************************************\
- * Hue web server additions
-\*********************************************************************************************/
-
-//10http://{x1:80/urn:schemas-upnp-org:device:Basic:1Amazon-Echo-HA-Bridge ({x1)Royal Philips Electronicshttp://www.philips.comPhilips hue Personal Wireless LightingPhilips hue bridge 2012929000226503{x3uuid:{x2\r\n\r\n
-//Successfully compressed from 625 to 391 bytes (-37.4%)
-const size_t HUE_DESCRIPTION_XML_SIZE = 625;
-const char HUE_DESCRIPTION_XML_COMPRESSED[] PROGMEM = "\x3D\x0E\xD1\xB0\x68\x48\xCD\xFF\xDB\x9C\x7C\x3D\x87\x21\xD1\x9E\xC3\xB4\x7E\x1E"
- "\x85\xFC\xCA\x46\xC1\xA1\x77\x8F\x87\xB0\x5F\xF8\xF3\xF0\x62\x98\xDB\xF1\xD6\x2C"
- "\x67\x0C\x3A\xF3\xE3\xC7\x98\x8C\xCF\x43\x67\x59\xC8\x75\xB3\xD8\x7E\x1E\x85\xE1"
- "\x8C\x32\x33\x04\x1C\x78\xFC\x3D\x06\xD9\xAF\x3E\x7E\x1C\x87\xA1\xD8\x40\x83\x14"
- "\xF4\x1B\xBD\x9F\x3F\x0E\x33\xD0\xEC\x20\x41\x8A\x7A\x1D\x80\x91\x85\x10\xB2\xF9"
- "\x04\x43\xAF\xCC\xFC\x15\x54\x30\xF3\x3B\x0E\xC3\xDA\x6C\x39\x0F\x3F\xB3\xB0\xF4"
- "\x3B\x08\x10\xEA\x1E\x80\x83\xA2\x82\x1C\x42\xA3\x21\x8C\xFC\x05\x6D\xB4\xF3\x21"
- "\xD7\xED\x0C\xF3\x39\x0F\x43\xB0\x81\x1B\x0C\x3D\x0C\x7F\x5F\x08\x11\x91\x75\x8D"
- "\x67\xE1\x58\xDB\x36\xE7\x1D\x64\xC3\x15\x87\x59\x0A\x2B\x3A\xC8\x77\xF4\x41\xE6"
- "\x8E\xE9\xED\x36\x1C\x87\x78\xF4\x3B\x08\x12\x30\x63\xD0\x6D\xF0\xB3\x16\x1D\x0B"
- "\xFB\xF9\xF8\x5F\xC3\x2B\x09\x10\xC1\x5A\x16\x8C\xF2\x26\x13\x0E\xBF\x9D\xA1\xF8"
- "\xF4\x3B\x01\x23\x04\x04\x8C\x48\x85\x97\xC8\x20\x43\xE0\xDC\x7C\x7C\x7C\xE8\x30"
- "\x10\x71\xA3\xA0\x78\x34\x12\x71\x22\x16\x5F\x20\x8F\xC3\xD0\x6E\x08\xC2\x21\x1F"
- "\x83\xFE\x8C\xAD\xCE\x3F\x01\x0F\x49\x14\x2D\xA2\x18\xFF\xEC\xEB\x09\x10\xFE\xFD"
- "\x84\xFD\xE4\x41\x68\xF0\xAA\xDE\x1E\x3D\x0E\xC0\x4C\xC5\x41\x07\x27\x2E\xB1\xAC"
- "\x12\x32\x01\xC0\x83\xC2\x41\xCA\x72\x88\x10\xB1\x10\x42\xE1\x13\x04\x61\x17\x0B"
- "\x1A\x39\xFC\xFC\x38\xA9\x36\xEA\xBB\x5D\x90\x21\xE0\x20\x83\x58\xF4\xF3\xFE\xD8"
- "\x21\xCA\x3D\xA6\xC3\x96\x7A\x1D\x84\x09\x13\x8F\x42\x16\x42\x17\x1F\x82\xC5\xE8"
- "\x87\x99\xED\x36\x1C\xA3\xD0\xEC\x22\x16\x42\x17\x1F\x80\x87\xC7\x19\xF8\x7A\x1D"
- "\x9F\xCC\xA3\xF2\x70\xA4\x6E\x9C\x29\x1B\x8D";
-// const char HUE_DESCRIPTION_XML[] PROGMEM =
-// ""
-// ""
-// ""
-// "1"
-// "0"
-// ""
-// "http://{x1:80/"
-// ""
-// "urn:schemas-upnp-org:device:Basic:1"
-// "Amazon-Echo-HA-Bridge ({x1)"
-// "Royal Philips Electronics"
-// "http://www.philips.com"
-// "Philips hue Personal Wireless Lighting"
-// "Philips hue bridge 2012"
-// "929000226503"
-// "{x3"
-// "uuid:{x2"
-// ""
-// "\r\n"
-// "\r\n";
-
-const char HUE_LIGHTS_U[] PROGMEM =
- //=HUE_LIGHTS_STATUS_JSON1_SUFFIX
- "%s\"alert\":\"none\","
- "\"effect\":\"none\","
- "\"reachable\":true}"
- "\0"
- //=HUE_LIGHTS_STATUS_JSON2
- ",\"type\":\"Extended color light\","
- "\"name\":\"%s\","
- "\"modelid\":\"%s\","
- "\"manufacturername\":\"%s\","
- "\"uniqueid\":\"%s\"}"
- "\0"
- //=HUE_GROUP0_STATUS_JSON
- "{\"name\":\"Group 0\","
- "\"lights\":[{l1],"
- "\"type\":\"LightGroup\","
- "\"action\":"
- "\0"
- //=HUE_ERROR_JSON
- "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]"
- "\0"
- //=HUE_RESP_ON
- "{\"success\":{\"/lights/%d/state/on\":%s}}"
- "\0"
- //=HUE_RESP_NUM
- "{\"success\":{\"/lights/%d/state/%s\":%d}}"
- "\0"
- //=HUE_RESP_XY
- "{\"success\":{\"/lights/%d/state/xy\":[%s,%s]}}"
- "\0"
- ;
-
-// Use the tool at https://tasmota.hadinger.fr/util and choose "Compress Strings template with Unishox"
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT BELOW ++++++++++++++++++++
-// ++++++++++++++++++++vvvvvvvvvvvvvvvvvvv++++++++++++++++++++
-enum {
- HUE_LIGHTS_STATUS_JSON1_SUFFIX=0,
- HUE_LIGHTS_STATUS_JSON2=51,
- HUE_GROUP0_STATUS_JSON=150,
- HUE_ERROR_JSON=213,
- HUE_RESP_ON=283,
- HUE_RESP_NUM=322,
- HUE_RESP_XY=361,
-};
-
-// Compressed from 405 to 275, -32.1%
-const char HUE_LIGHTS[] PROGMEM = "\x00\x1A\x3E\xBC\x7B\x2C\x27\xFA\x3D\x87\x99\xEC\xEC\xE6\x7B\x0E\xA3\xD8\xCC\x18"
- "\x61\x82\x34\xCF\xBB\x0C\x55\x8E\x09\x9E\xC3\xCE\xBE\x2D\x9E\xE9\xC2\xF9\xD4\x7B"
- "\x28\xC8\x63\x3D\x87\x99\xEC\x26\x6C\xA7\xC2\x31\x10\x78\x16\x7D\x05\xA3\xC2\xA8"
- "\xF6\x1D\x47\xB3\xAC\x6B\x3D\x87\x99\xEC\x3E\xBC\x7B\x0E\xA3\xD8\x37\x04\x61\x68"
- "\x80\x89\x2E\xF8\x59\x8B\x0E\x85\xFD\xFC\x11\xF0\x31\x7D\xA6\xA1\x6C\x10\xF0\x43"
- "\xDD\x38\x5F\x3D\xA0\x87\x90\x90\xF7\xF0\x58\xC4\x71\x9E\xC3\xA8\xF6\x10\x5A\x3C"
- "\x2A\x2B\xBC\x7B\x0F\x33\xDE\x3D\xA1\x1C\x87\xBE\x40\x89\xAF\x90\x5A\x3C\x2A\x2B"
- "\x88\x7B\xF8\x2C\x61\xEC\x3A\x8F\x65\x87\x5B\x9C\x7B\x0F\x39\xC2\xF9\xEF\x1E\xD3"
- "\xD8\xFF\xFC\xF9\xEC\x3C\xCF\x68\x21\x60\xA7\x13\x87\x51\xEC\x2B\x10\x4F\xBF\x78"
- "\xF6\x1E\x67\xB0\xEC\x3D\x87\x51\xEC\x11\xF8\x3F\xE8\xC0\x41\xC3\xCF\x61\x6F\x53"
- "\xFF\x58\x48\x9F\xFF\x9F\x3D\x87\xB8\xF7\x1E\xFC\xE1\x7C\xF6\x9E\xCF\x0B\x0C\x37"
- "\xEF\x1E\xC3\xCC\xF6\x9E\xC3\xB0\x10\x75\xC3\xB0\xFA\x10\xEC\xF5\x5D\x33\xB3\x38"
- "\xF6\x1E\x67\xD7\x8F\x71\xEE\x05\xAC\x0C\xFA\xF1\xEC\x3C\xCF\xA1\x01\x73\x03\x36"
- "\x19\x1E\xC3\xCC\xF7\x8F\xAF\x1D\x47\xD7\x8F\x7C\xF7\x1E\xE9\xC2\xF9";
-// ++++++++++++++++++++^^^^^^^^^^^^^^^^^^^++++++++++++++++++++
-// ++++++++++++++++++++ DO NOT EDIT ABOVE ++++++++++++++++++++
-// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
-
-
-
-// const char HUE_LIGHTS_STATUS_JSON1_SUFFIX[] PROGMEM =
-// "%s\"alert\":\"none\","
-// "\"effect\":\"none\","
-// "\"reachable\":true}";
-
-// const char HUE_LIGHTS_STATUS_JSON2[] PROGMEM =
-// ",\"type\":\"Extended color light\","
-// "\"name\":\"%s\","
-// "\"modelid\":\"%s\","
-// "\"manufacturername\":\"%s\","
-// "\"uniqueid\":\"%s\"}";
-
-// const char HUE_GROUP0_STATUS_JSON[] PROGMEM =
-// "{\"name\":\"Group 0\","
-// "\"lights\":[{l1],"
-// "\"type\":\"LightGroup\","
-// "\"action\":";
-
-// const char HueConfigResponse_JSON[] PROGMEM =
-// "{\"name\":\"Philips hue\","
-// "\"mac\":\"{ma\","
-// "\"dhcp\":true,"
-// "\"ipaddress\":\"{ip\","
-// "\"netmask\":\"{ms\","
-// "\"gateway\":\"{gw\","
-// "\"proxyaddress\":\"none\","
-// "\"proxyport\":0,"
-// "\"bridgeid\":\"{br\","
-// "\"UTC\":\"{dt\","
-// "\"whitelist\":{\"{id\":{"
-// "\"last use date\":\"{dt\","
-// "\"create date\":\"{dt\","
-// "\"name\":\"Remote\"}},"
-// "\"swversion\":\"01041302\","
-// "\"apiversion\":\"1.17.0\","
-// "\"swupdate\":{\"updatestate\":0,\"url\":\"\",\"text\":\"\",\"notify\": false},"
-// "\"linkbutton\":false,"
-// "\"portalservices\":false"
-// "}";
-//{"name":"Philips hue","mac":"{ma","dhcp":true,"ipaddress":"{ip","netmask":"{ms","gateway":"{gw","proxyaddress":"none","proxyport":0,"bridgeid":"{br","UTC":"{dt","whitelist":{"{id":{"last use date":"{dt","create date":"{dt","name":"Remote"}},"swversion":"01041302","apiversion":"1.17.0","swupdate":{"updatestate":0,"url":"","text":"","notify": false},"linkbutton":false,"portalservices":false}
-const size_t HueConfigResponse_JSON_SIZE = 392;
-const char HueConfigResponse_JSON[] PROGMEM = "\x3D\xA7\xB3\xAC\x6B\x3D\x87\x99\xEC\x21\x82\xB4\x2D\x19\xE4\x28\x5B\x3D\x87\x51"
- "\xEC\x1B\x61\x9E\xC3\xCC\xF6\x1E\xD1\xB6\x7B\x0E\xA3\xD8\x20\xA0\xC6\x1E\xC3\xCE"
- "\xBE\x2D\x9D\x47\xB3\x46\x58\x82\x7D\xFB\xC7\xB0\xF3\x3D\x87\xB7\x46\x1E\xC3\xA8"
- "\xF6\x73\xA1\xB7\xE3\x43\xD8\x79\x9E\xC3\xDA\x37\xC7\xB0\xEA\x3D\x83\xD7\x4C\x7E"
- "\xCC\x8F\x61\xE6\x7B\x0F\x68\xF0\xF9\xEC\x3A\x8F\x60\xCF\xE1\xB0\xC8\x11\x71\x1E"
- "\xCE\x60\x87\x48\x66\x7E\x8F\x61\xE6\x71\x9D\x47\xB0\x87\x7F\x44\x1E\x7A\x21\xEC"
- "\x3C\xCF\x61\xED\x1D\xF3\xD8\x75\x1E\xC2\x16\x54\x41\x9E\xC3\xCC\xF6\x1E\xD1\x28"
- "\xF6\x1D\x47\xB0\x7C\x56\xD3\x0B\x7D\x47\xB0\xF3\x3D\xA7\xB0\xF6\xE8\x87\xB0\xF3"
- "\x3D\xA7\xB0\x2B\xF5\x21\x7E\x68\x4B\xA6\x08\x98\x30\x7F\x77\x40\x95\x40\x10\xB8"
- "\x3A\x2F\xB1\xB9\x4C\xF6\x1E\xE3\xDC\x75\x1E\xCF\x0F\x99\xBF\xFB\x73\x8F\x61\xE6"
- "\x7B\x0E\x38\xF2\x5B\xA3\xD8\x75\x1E\xC2\xB1\x9A\x08\xB5\x0E\x43\xA4\xF1\xD1\x9E"
- "\xC3\xA8\xF6\x17\x87\xC5\x8C\x04\x1C\xB0\xF6\x9E\xC0\x41\x8D\xEA\xBA\x67\xB0\xF3"
- "\x38\xCE\xA3\xD8\x42\xFE\x11\xEC\x3C\xCF\x61\xEC\x3A\x8F\x65\x33\x65\x02\x0C\x6E"
- "\xCA\xD3\x06\x47\xB0\xF3\x46\x2C\x2F\x33\xDC\x75\x1E\xC0\xB7\x8D\x07\x0B\xAA\xCE"
- "\x3D\x87\x99\x8B\x0B\xCC\xEA\x3D\x83\x33\xF5\x61\x79\xFC\xCF\x43\x7E\x04\x2A\x2B"
- "\x67\xB8";
-
-// const char HUE_ERROR_JSON[] PROGMEM =
-// "[{\"error\":{\"type\":901,\"address\":\"/\",\"description\":\"Internal Error\"}}]";
-
-/********************************************************************************************/
-
-String GetHueDeviceId(uint16_t id)
-{
- String deviceid = WiFi.macAddress();
- deviceid += F(":00:11-");
- if(id<9) deviceid += F("0");
- deviceid += String(id);
- deviceid.toLowerCase();
- return deviceid; // 5c:cf:7f:13:9f:3d:00:11-1
-}
-
-String GetHueUserId(void)
-{
- char userid[7];
-
- snprintf_P(userid, sizeof(userid), PSTR("%03x"), ESP_getChipId());
- return String(userid);
-}
-
-void HandleUpnpSetupHue(void)
-{
- AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_HUE_BRIDGE_SETUP));
- String description_xml = Decompress(HUE_DESCRIPTION_XML_COMPRESSED,HUE_DESCRIPTION_XML_SIZE);
- description_xml.replace(F("{x1"), WiFi.localIP().toString());
- description_xml.replace(F("{x2"), HueUuid());
- description_xml.replace(F("{x3"), HueSerialnumber());
- WSSend(200, CT_XML, description_xml);
-}
-
-void HueNotImplemented(String *path)
-{
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API_NOT_IMPLEMENTED " (%s)"), path->c_str());
-
- WSSend(200, CT_APP_JSON, PSTR("{}"));
-}
-
-void HueConfigResponse(String *response)
-{
- *response += Decompress(HueConfigResponse_JSON, HueConfigResponse_JSON_SIZE);
- response->replace(F("{ma"), WiFi.macAddress());
- response->replace(F("{ip"), WiFi.localIP().toString());
- response->replace(F("{ms"), WiFi.subnetMask().toString());
- response->replace(F("{gw"), WiFi.gatewayIP().toString());
- response->replace(F("{br"), HueBridgeId());
- response->replace(F("{dt"), GetDateAndTime(DT_UTC));
- response->replace(F("{id"), GetHueUserId());
-}
-
-void HueConfig(String *path)
-{
- String response = "";
- HueConfigResponse(&response);
- WSSend(200, CT_APP_JSON, response);
-}
-
-// device is forced to CT mode instead of HSB
-// only makes sense for LST_COLDWARM, LST_RGBW and LST_RGBCW
-bool g_gotct = false;
-
-// store previously set values from the Alexa app
-// it allows to correct slight deviations from value set by the app
-// The Alexa app is very sensitive to exact values
-uint16_t prev_hue = 0;
-uint8_t prev_sat = 0;
-uint8_t prev_bri = 254;
-uint16_t prev_ct = 254;
-char prev_x_str[24] = "\0"; // store previously set xy by Alexa app
-char prev_y_str[24] = "\0";
-
-#ifdef USE_LIGHT
-uint8_t getLocalLightSubtype(uint8_t device) {
- if (TasmotaGlobal.light_type) {
- if (device >= Light.device) {
- if (Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- return LST_SINGLE; // If SetOption68, each channel acts like a dimmer
- } else {
- return Light.subtype; // the actual light
- }
- } else {
- return LST_NONE; // relays
- }
- } else {
- return LST_NONE;
- }
-}
-
-void HueLightStatus1(uint8_t device, String *response)
-{
- uint16_t ct = 0;
- uint8_t color_mode;
- String light_status = "";
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint32_t echo_gen = findEchoGeneration(); // 1 for 1st gen =+ Echo Dot 2nd gen, 2 for 2nd gen and above
- // local_light_subtype simulates the Light.subtype for 'device'
- // For relays LST_NONE, for dimmers LST_SINGLE
- uint8_t local_light_subtype = getLocalLightSubtype(device);
-
- bri = LightGetBri(device); // get Dimmer corrected with SetOption68
- if (bri > 254) bri = 254; // Philips Hue bri is between 1 and 254
- if (bri < 1) bri = 1;
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- bri = (float)((Settings.shutter_options[device-1] & 1) ? 100 - Settings.shutter_position[device-1] : Settings.shutter_position[device-1]) / 100;
- }
-#endif
-
- if (TasmotaGlobal.light_type) {
- light_state.getHSB(&hue, &sat, nullptr);
- if (sat > 254) sat = 254; // Philips Hue only accepts 254 as max hue
- hue = changeUIntScale(hue, 0, 360, 0, 65535);
-
- if ((sat != prev_sat) || (hue != prev_hue)) { // if sat or hue was changed outside of Alexa, reset xy
- prev_x_str[0] = prev_y_str[0] = 0;
- }
-
- color_mode = light_state.getColorMode();
- ct = light_state.getCT();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- }
-
- const size_t buf_size = 256;
- char * buf = (char*) malloc(buf_size); // temp buffer for strings, avoid stack
- UnishoxStrings msg(HUE_LIGHTS);
-
- snprintf_P(buf, buf_size, PSTR("{\"on\":%s,"), (TasmotaGlobal.power & (1 << (device-1))) ? PSTR("true") : PSTR("false"));
- // Brightness for all devices with PWM
- if ((1 == echo_gen) || (LST_SINGLE <= local_light_subtype)) { // force dimmer for 1st gen Echo
- snprintf_P(buf, buf_size, PSTR("%s\"bri\":%d,"), buf, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- snprintf_P(buf, buf_size, PSTR("%s\"colormode\":\"%s\","), buf, g_gotct ? PSTR("ct") : PSTR("hs"));
- }
- if (LST_RGB <= local_light_subtype) { // colors
- if (prev_x_str[0] && prev_y_str[0]) {
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, prev_x_str, prev_y_str);
- } else {
- float x, y;
- light_state.getXY(&x, &y);
- snprintf_P(buf, buf_size, PSTR("%s\"xy\":[%s,%s],"), buf, String(x, 5).c_str(), String(y, 5).c_str());
- }
- snprintf_P(buf, buf_size, PSTR("%s\"hue\":%d,\"sat\":%d,"), buf, hue, sat);
- }
- if (LST_COLDWARM == local_light_subtype || LST_RGBW <= local_light_subtype) { // white temp
- snprintf_P(buf, buf_size, PSTR("%s\"ct\":%d,"), buf, ct > 0 ? ct : 284);
- }
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON1_SUFFIX], buf);
-
- *response += buf;
- free(buf);
-}
-
-// Check whether this device should be reported to Alexa or considered hidden.
-// Any device whose friendly name start with "$" is considered hidden
-bool HueActive(uint8_t device) {
- if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
- return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
-}
-
-void HueLightStatus2(uint8_t device, String *response)
-{
- const size_t buf_size = 300;
- char * buf = (char*) malloc(buf_size);
- const size_t max_name_len = 32;
- char fname[max_name_len + 1];
-
- strlcpy(fname, SettingsText(device <= MAX_FRIENDLYNAMES ? SET_FRIENDLYNAME1 + device -1 : SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1), max_name_len + 1);
-
- if (device > MAX_FRIENDLYNAMES) {
- uint32_t fname_len = strlen(fname);
- if (fname_len > max_name_len - 2) { fname_len = max_name_len - 2; }
- fname[fname_len++] = '-';
- if (device - MAX_FRIENDLYNAMES < 10) {
- fname[fname_len++] = '0' + device - MAX_FRIENDLYNAMES;
- } else {
- fname[fname_len++] = 'A' + device - MAX_FRIENDLYNAMES - 10;
- }
- fname[fname_len] = 0x00;
- }
- UnishoxStrings msg(HUE_LIGHTS);
- snprintf_P(buf, buf_size, msg[HUE_LIGHTS_STATUS_JSON2],
- EscapeJSONString(fname).c_str(),
- EscapeJSONString(Settings.user_template_name).c_str(),
- PSTR("Tasmota"),
- GetHueDeviceId(device).c_str());
- *response += buf;
- free(buf);
-}
-#endif // USE_LIGHT
-
-// generate a unique lightId mixing local IP address and device number
-// it is limited to 32 devices.
-// last 24 bits of Mac address + 4 bits of local light + high bit for relays 16-31, relay 32 is mapped to 0
-// Zigbee extension: bit 29 = 1, and last 16 bits = short address of Zigbee device
-#ifndef USE_ZIGBEE
-uint32_t EncodeLightId(uint8_t relay_id)
-#else
-uint32_t EncodeLightId(uint8_t relay_id, uint16_t z_shortaddr = 0)
-#endif
-{
- uint8_t mac[6];
- WiFi.macAddress(mac);
- uint32_t id = (mac[3] << 20) | (mac[4] << 12) | (mac[5] << 4);
-
- if (relay_id >= 32) { // for Relay #32, we encode as 0
- relay_id = 0;
- }
- if (relay_id > 15) {
- id |= (1 << 28);
- }
- id |= (relay_id & 0xF);
-#ifdef USE_ZIGBEE
- if ((z_shortaddr) && (!relay_id)) {
- // fror Zigbee devices, we have relay_id == 0 and shortaddr != 0
- id = (1 << 29) | z_shortaddr;
- }
-#endif
-
- return id;
-}
-
-
-// get hue_id and decode the relay_id
-// 4 LSB decode to 1-15, if bit 28 is set, it encodes 16-31, if 0 then 32
-// Zigbee:
-// If the Id encodes a Zigbee device (meaning bit 29 is set)
-// it returns 0 and sets the 'shortaddr' to the device short address
-#ifndef USE_ZIGBEE
-uint32_t DecodeLightId(uint32_t hue_id)
-#else
-uint32_t DecodeLightId(uint32_t hue_id, uint16_t * shortaddr = nullptr)
-#endif
-{
- uint8_t relay_id = hue_id & 0xF;
- if (hue_id & (1 << 28)) { // check if bit 25 is set, if so we have
- relay_id += 16;
- }
- if (0 == relay_id) { // special value 0 is actually relay #32
- relay_id = 32;
- }
-#ifdef USE_ZIGBEE
- if (shortaddr) { *shortaddr = 0x0000; }
- if (hue_id & (1 << 29)) {
- // this is actually a Zigbee ID
- if (shortaddr) { *shortaddr = hue_id & 0xFFFF; }
- relay_id = 0;
- }
-#endif // USE_ZIGBEE
- return relay_id;
-}
-
-// Check if the Echo device is of 1st generation, which triggers different results
-inline uint32_t findEchoGeneration(void) {
- // don't try to guess from User-Agent anymore but use SetOption109
- return Settings.flag4.alexa_gen_1 ? 1 : 2;
-}
-
-void HueGlobalConfig(String *path) {
- String response;
-
- path->remove(0,1); // cut leading / to get
- response = F("{\"lights\":{");
- bool appending = false; // do we need to add a comma to append
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
- response += F("},\"groups\":{},\"schedules\":{},\"config\":");
- HueConfigResponse(&response);
- response += F("}");
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HueAuthentication(String *path)
-{
- char response[38];
-
- snprintf_P(response, sizeof(response), PSTR("[{\"success\":{\"username\":\"%s\"}}]"), GetHueUserId().c_str());
- WSSend(200, CT_APP_JSON, response);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Authentication Result (%s)"), response);
-}
-
-#ifdef USE_LIGHT
-// refactored to remove code duplicates
-void CheckHue(String * response, bool &appending) {
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- for (uint32_t i = 1; i <= maxhue; i++) {
- if (HueActive(i)) {
- if (appending) { *response += ","; }
- *response += F("\"");
- *response += EncodeLightId(i);
- *response += F("\":{\"state\":");
- HueLightStatus1(i, response);
- HueLightStatus2(i, response);
- appending = true;
- }
- }
-}
-
-void HueLightsCommand(uint8_t device, uint32_t device_id, String &response) {
- uint16_t hue = 0;
- uint8_t sat = 0;
- uint8_t bri = 254;
- uint16_t ct = 0;
- bool on = false;
- bool resp = false; // is the response non null (add comma between parameters)
- bool change = false; // need to change a parameter to the light
- uint8_t local_light_subtype = getLocalLightSubtype(device); // get the subtype for this device
-
- const size_t buf_size = 100;
- char * buf = (char*) malloc(buf_size);
- UnishoxStrings msg(HUE_LIGHTS);
-
- if (Webserver->args()) {
- response = "[";
-
- JsonParser parser((char*) Webserver->arg((Webserver->args())-1).c_str());
- JsonParserObject root = parser.getRootObject();
-
- JsonParserToken hue_on = root[PSTR("on")];
- if (hue_on) {
- on = hue_on.getBool();
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_ON],
- device_id, on ? PSTR("true") : PSTR("false"));
-
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- if (!change) {
- bri = on ? 1.0f : 0.0f; // when bri is not part of this request then calculate it
- change = true;
- resp = true;
- response += buf; // actually publish the state
- }
- } else {
-#endif
- ExecuteCommandPower(device, (on) ? POWER_ON : POWER_OFF, SRC_HUE);
- response += buf;
- resp = true;
-#ifdef USE_SHUTTER
- }
-#endif // USE_SHUTTER
- }
-
- if (TasmotaGlobal.light_type && (local_light_subtype >= LST_SINGLE)) {
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- light_state.getHSB(&hue, &sat, nullptr);
- bri = light_state.getBri(); // get the combined bri for CT and RGB, not only the RGB one
- ct = light_state.getCT();
- uint8_t color_mode = light_state.getColorMode();
- if (LCM_RGB == color_mode) { g_gotct = false; }
- if (LCM_CT == color_mode) { g_gotct = true; }
- // If LCM_BOTH == color_mode, leave g_gotct unchanged
- } else { // treat each channel as simple dimmer
- bri = LightGetBri(device);
- }
- }
- prev_x_str[0] = prev_y_str[0] = 0; // reset xy string
-
- parser.setCurrent();
- JsonParserToken hue_bri = root[PSTR("bri")];
- if (hue_bri) { // Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum). Note: a brightness of 1 is not off.
- bri = hue_bri.getUInt();
- prev_bri = bri; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("bri"), bri);
- response += buf;
- if (LST_SINGLE <= Light.subtype) {
- // extend bri value if set to max
- if (254 <= bri) { bri = 255; }
- change = true;
- }
- resp = true;
- }
-
- // handle xy before Hue/Sat
- // If the request contains both XY and HS, we wan't to give priority to HS
- parser.setCurrent();
- JsonParserToken hue_xy = root[PSTR("xy")];
- if (hue_xy) {
- JsonParserArray arr_xy = JsonParserArray(hue_xy);
- JsonParserToken tok_x = arr_xy[0];
- JsonParserToken tok_y = arr_xy[1];
- float x = tok_x.getFloat();
- float y = tok_y.getFloat();
- strlcpy(prev_x_str, tok_x.getStr(), sizeof(prev_x_str));
- strlcpy(prev_y_str, tok_y.getStr(), sizeof(prev_y_str));
- uint8_t rr,gg,bb;
- XyToRgb(x, y, &rr, &gg, &bb);
- RgbToHsb(rr, gg, bb, &hue, &sat, nullptr);
- prev_hue = changeUIntScale(hue, 0, 360, 0, 65535); // calculate back prev_hue
- prev_sat = (sat > 254 ? 254 : sat);
- //AddLog(LOG_LEVEL_DEBUG_MORE, "XY RGB (%d %d %d) HS (%d %d)", rr,gg,bb,hue,sat);
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_XY],
- device_id, prev_x_str, prev_y_str);
- response += buf;
- g_gotct = false;
- resp = true;
- change = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_hue = root[PSTR("hue")];
- if (hue_hue) { // The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.
- hue = hue_hue.getUInt();
- prev_hue = hue;
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("hue"), hue);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // change range from 0..65535 to 0..360
- hue = changeUIntScale(hue, 0, 65535, 0, 360);
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_sat = root[PSTR("sat")];
- if (hue_sat) { // Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).
- sat = hue_sat.getUInt();
- prev_sat = sat; // store command value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("sat"), sat);
- response += buf;
- if (LST_RGB <= Light.subtype) {
- // extend sat value if set to max
- if (254 <= sat) { sat = 255; }
- g_gotct = false;
- change = true;
- }
- resp = true;
- }
-
- parser.setCurrent();
- JsonParserToken hue_ct = root[PSTR("ct")];
- if (hue_ct) { // Color temperature 153 (Cold) to 500 (Warm)
- ct = hue_ct.getUInt();
- prev_ct = ct; // store commande value
- if (resp) { response += ","; }
- snprintf_P(buf, buf_size,
- msg[HUE_RESP_NUM],
- device_id, PSTR("ct"), ct);
- response += buf;
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
- g_gotct = true;
- change = true;
- }
- resp = true;
- }
-
- if (change) {
-#ifdef USE_SHUTTER
- if (ShutterState(device)) {
- AddLog(LOG_LEVEL_DEBUG, PSTR("Settings.shutter_invert: %d"), Settings.shutter_options[device-1] & 1);
- ShutterSetPosition(device, bri * 100.0f );
- } else
-#endif
- if (TasmotaGlobal.light_type && (local_light_subtype > LST_NONE)) { // not relay
- if (!Settings.flag3.pwm_multi_channels) { // SetOption68 - Enable multi-channels PWM instead of Color PWM
- if (g_gotct) {
- light_controller.changeCTB(ct, bri);
- } else {
- light_controller.changeHSB(hue, sat, bri);
- }
- LightPreparePower();
- } else { // SetOption68 On, each channel is a dimmer
- LightSetBri(device, bri);
- }
- if (LST_COLDWARM <= local_light_subtype) {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
- } else {
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_DIMMER));
- }
- XdrvRulesProcess();
- }
- change = false;
- }
- response += "]";
- if (2 == response.length()) {
- response = msg[HUE_ERROR_JSON];
- }
- }
- else {
- response = msg[HUE_ERROR_JSON];
- }
- free(buf);
-}
-#endif // USE_LIGHT
-
-void HueLights(String *path)
-{
-/*
- * http://tasmota/api/username/lights/1/state?1={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
- String response;
- int code = 200;
- uint8_t device = 1;
- uint32_t device_id; // the raw device_id used by Hue emulation
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
-
- path->remove(0,path->indexOf(F("/lights"))); // Remove until /lights
- if (path->endsWith(F("/lights"))) { // Got /lights
- response = F("{");
- bool appending = false;
-#ifdef USE_LIGHT
- CheckHue(&response, appending);
-#endif // USE_LIGHT
-#ifdef USE_ZIGBEE
- ZigbeeCheckHue(&response, appending);
-#endif // USE_ZIGBEE
-#ifdef USE_SCRIPT_HUE
- Script_Check_Hue(&response);
-#endif
- response += F("}");
- }
- else if (path->endsWith(F("/state"))) { // Got ID/state
- path->remove(0,8); // Remove /lights/
- path->remove(path->indexOf(F("/state"))); // Remove /state
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- return ZigbeeHandleHue(shortaddr, device_id, response);
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- return Script_Handle_Hue(path);
- }
-#endif
-#ifdef USE_LIGHT
- if ((device >= 1) || (device <= maxhue)) {
- HueLightsCommand(device, device_id, response);
- }
-#endif // USE_LIGHT
-
- }
- else if(path->indexOf(F("/lights/")) >= 0) { // Got /lights/ID
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("/lights path=%s"), path->c_str());
- path->remove(0,8); // Remove /lights/
- device_id = atoi(path->c_str());
- device = DecodeLightId(device_id);
-#ifdef USE_ZIGBEE
- uint16_t shortaddr;
- device = DecodeLightId(device_id, &shortaddr);
- if (shortaddr) {
- ZigbeeHueStatus(&response, shortaddr);
- goto exit;
- }
-#endif // USE_ZIGBEE
-
-#ifdef USE_SCRIPT_HUE
- if (device > TasmotaGlobal.devices_present) {
- Script_HueStatus(&response, device-TasmotaGlobal.devices_present - 1);
- goto exit;
- }
-#endif
-
-#ifdef USE_LIGHT
- if ((device < 1) || (device > maxhue)) {
- device = 1;
- }
- response += F("{\"state\":");
- HueLightStatus1(device, &response);
- HueLightStatus2(device, &response);
-#endif // USE_LIGHT
- }
- else {
- response = F("{}");
- code = 406;
- }
- exit:
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " Result (%s)"), response.c_str());
- WSSend(code, CT_APP_JSON, response);
-}
-
-void HueGroups(String *path)
-{
-/*
- * http://tasmota/api/username/groups?1={"name":"Woonkamer","lights":[],"type":"Room","class":"Living room"})
- */
- String response(F("{}"));
- uint8_t maxhue = (TasmotaGlobal.devices_present > MAX_HUE_DEVICES) ? MAX_HUE_DEVICES : TasmotaGlobal.devices_present;
- //AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups (%s)"), path->c_str());
-
- if (path->endsWith(F("/0"))) {
- UnishoxStrings msg(HUE_LIGHTS);
- response = msg[HUE_GROUP0_STATUS_JSON];
- String lights = F("\"1\"");
- for (uint32_t i = 2; i <= maxhue; i++) {
- lights += F(",\"");
- lights += EncodeLightId(i);
- lights += F("\"");
- }
-
-#ifdef USE_ZIGBEE
- ZigbeeHueGroups(&response);
-#endif // USE_ZIGBEE
- response.replace(F("{l1"), lights);
-#ifdef USE_LIGHT
- HueLightStatus1(1, &response);
-#endif // USE_LIGHT
- response += F("}");
- }
-
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE " HueGroups Result (%s)"), path->c_str());
- WSSend(200, CT_APP_JSON, response);
-}
-
-void HandleHueApi(String *path)
-{
- /* HUE API uses /api// syntax. The userid is created by the echo device and
- * on original HUE the pressed button allows for creation of this user. We simply ignore the
- * user part and allow every caller as with Web or WeMo.
- *
- * (c) Heiko Krupp, 2017
- *
- * Hue URL
- * http://tasmota/api/username/lights/1/state with post data {"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- * is converted by webserver to
- * http://tasmota/api/username/lights/1/state with arg plain={"on":true,"hue":56100,"sat":254,"bri":254,"alert":"none","transitiontime":40}
- */
-
- uint8_t args = 0;
-
- path->remove(0, 4); // remove /api
- uint16_t apilen = path->length();
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_API " (%s) from %s"), path->c_str(), Webserver->client().remoteIP().toString().c_str()); // HTP: Hue API (//lights/1/state) from 192.168.1.20
- for (args = 0; args < Webserver->args(); args++) {
- String json = Webserver->arg(args);
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_HTTP D_HUE_POST_ARGS " (%s)"), json.c_str()); // HTP: Hue POST args ({"on":false})
- }
-
- UnishoxStrings msg(HUE_API);
- if (path->endsWith(msg[HUE_INVALID])) {} // Just ignore
- else if (!apilen) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_ROOT])) HueAuthentication(path); // New HUE App setup
- else if (path->endsWith(msg[HUE_CONFIG])) HueConfig(path);
- else if (path->indexOf(msg[HUE_LIGHTS_API]) >= 0) HueLights(path);
- else if (path->indexOf(msg[HUE_GROUPS]) >= 0) HueGroups(path);
- else if (path->endsWith(msg[HUE_SCHEDULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SENSORS])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_SCENES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RULES])) HueNotImplemented(path);
- else if (path->endsWith(msg[HUE_RESOURCELINKS])) HueNotImplemented(path);
- else HueGlobalConfig(path);
-}
-
-/*********************************************************************************************\
- * Interface
-\*********************************************************************************************/
-
-bool Xdrv20(uint8_t function)
-{
- bool result = false;
-
-#if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE)
- if ((EMUL_HUE == Settings.flag2.emulation)) {
-#else
- if (TasmotaGlobal.devices_present && (EMUL_HUE == Settings.flag2.emulation)) {
-#endif
- switch (function) {
- case FUNC_WEB_ADD_HANDLER:
- WebServer_on(PSTR("/description.xml"), HandleUpnpSetupHue);
- break;
- }
- }
- return result;
-}
-
-#endif // USE_WEBSERVER && USE_EMULATION && USE_EMULATION_HUE
From 8155d2c66a709386599d79a9090f78e634a247fa Mon Sep 17 00:00:00 2001
From: Stephan Hadinger
Date: Sun, 21 Mar 2021 17:12:10 +0100
Subject: [PATCH 23/35] Minor fixes
---
.../Berry-0.1.10/src/port/berry_conf.h | 10 +++++--
tasmota/xdrv_52_3_berry_tasmota.ino | 2 +-
tasmota/xdrv_52_9_berry.ino | 28 ++++++++++++++++++-
3 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
index 57fbe6208..74fc7f505 100644
--- a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
+++ b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h
@@ -163,8 +163,14 @@
* The default is to use the functions in the standard library.
**/
#ifdef USE_BERRY_PSRAM
- extern void *special_malloc(uint32_t size);
- extern void *special_realloc(void *ptr, size_t size);
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern void *berry_malloc(uint32_t size);
+ extern void *berry_realloc(void *ptr, size_t size);
+#ifdef __cplusplus
+}
+#endif
#define BE_EXPLICIT_MALLOC special_malloc
#define BE_EXPLICIT_REALLOC special_realloc
#else
diff --git a/tasmota/xdrv_52_3_berry_tasmota.ino b/tasmota/xdrv_52_3_berry_tasmota.ino
index bd9e1a8cc..e29382d18 100644
--- a/tasmota/xdrv_52_3_berry_tasmota.ino
+++ b/tasmota/xdrv_52_3_berry_tasmota.ino
@@ -317,7 +317,7 @@ extern "C" {
int32_t top = be_top(vm); // Get the number of arguments
if (top == 1 || (top == 2 && be_isint(vm, 2))) {
int32_t light_num = 0;
- if (top > 0) {
+ if (top > 1) {
light_num = be_toint(vm, 2);
}
push_getlight(vm, light_num);
diff --git a/tasmota/xdrv_52_9_berry.ino b/tasmota/xdrv_52_9_berry.ino
index dcdeb25b5..df2ba5b6b 100644
--- a/tasmota/xdrv_52_9_berry.ino
+++ b/tasmota/xdrv_52_9_berry.ino
@@ -47,6 +47,32 @@ void checkBeTop(void) {
}
}
+/*********************************************************************************************\
+ * Memory handler
+ * Use PSRAM if available
+\*********************************************************************************************/
+extern "C" {
+ void *berry_malloc(uint32_t size);
+ void *berry_realloc(void *ptr, size_t size);
+#ifdef USE_BERRY_PSRAM
+ void *berry_malloc(uint32_t size) {
+ return special_malloc(size);
+ }
+ void *berry_realloc(void *ptr, size_t size) {
+ return special_realloc(ptr, size);
+ }
+#else
+ void *berry_malloc(uint32_t size) {
+ return malloc(size);
+ }
+ void *berry_realloc(void *ptr, size_t size) {
+ return realloc(ptr, size);
+ }
+#endif // USE_BERRY_PSRAM
+
+}
+
+
/*********************************************************************************************\
* Handlers for Berry calls and async
*
@@ -564,7 +590,7 @@ void HandleBerryConsoleRefresh(void)
if (svalue.length()) {
berry.log.reset(); // clear all previous logs
berry.repl_active = true; // start recording
- AddLog_P(LOG_LEVEL_INFO, PSTR("BRY: received command %s"), svalue.c_str());
+ // AddLog_P(LOG_LEVEL_INFO, PSTR("BRY: received command %s"), svalue.c_str());
berry.log.addString(svalue.c_str(), nullptr, BERRY_CONSOLE_CMD_DELIMITER);
// Call berry
From 6c66b2d11fab156b81945a5acc10aba8fccf6858 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 21 Mar 2021 17:51:57 +0100
Subject: [PATCH 24/35] Add frequency to CSE7761 driver
---
tasmota/xdrv_03_energy.ino | 98 +++++++++++++++--------------------
tasmota/xnrg_19_cse7761.ino | 100 +++++++++++++++++++++++-------------
2 files changed, 105 insertions(+), 93 deletions(-)
diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino
index 702267f2c..c8d326bec 100644
--- a/tasmota/xdrv_03_energy.ino
+++ b/tasmota/xdrv_03_energy.ino
@@ -36,15 +36,16 @@
#define D_CMND_POWERCAL "PowerCal"
#define D_CMND_VOLTAGECAL "VoltageCal"
#define D_CMND_CURRENTCAL "CurrentCal"
+#define D_CMND_FREQUENCYCAL "FrequencyCal"
#define D_CMND_TARIFF "Tariff"
#define D_CMND_MODULEADDRESS "ModuleAddress"
enum EnergyCommands {
- CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL,
+ CMND_POWERCAL, CMND_VOLTAGECAL, CMND_CURRENTCAL, CMND_FREQUENCYCAL,
CMND_POWERSET, CMND_VOLTAGESET, CMND_CURRENTSET, CMND_FREQUENCYSET, CMND_MODULEADDRESS };
const char kEnergyCommands[] PROGMEM = "|" // No prefix
- D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|"
+ D_CMND_POWERCAL "|" D_CMND_VOLTAGECAL "|" D_CMND_CURRENTCAL "|" D_CMND_FREQUENCYCAL "|"
D_CMND_POWERSET "|" D_CMND_VOLTAGESET "|" D_CMND_CURRENTSET "|" D_CMND_FREQUENCYSET "|" D_CMND_MODULEADDRESS "|"
#ifdef USE_ENERGY_MARGIN_DETECTION
D_CMND_POWERDELTA "|" D_CMND_POWERLOW "|" D_CMND_POWERHIGH "|" D_CMND_VOLTAGELOW "|" D_CMND_VOLTAGEHIGH "|" D_CMND_CURRENTLOW "|" D_CMND_CURRENTHIGH "|"
@@ -57,7 +58,7 @@ const char kEnergyCommands[] PROGMEM = "|" // No prefix
D_CMND_ENERGYRESET "|" D_CMND_TARIFF ;
void (* const EnergyCommand[])(void) PROGMEM = {
- &CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal,
+ &CmndPowerCal, &CmndVoltageCal, &CmndCurrentCal, &CmndFrequencyCal,
&CmndPowerSet, &CmndVoltageSet, &CmndCurrentSet, &CmndFrequencySet, &CmndModuleAddress,
#ifdef USE_ENERGY_MARGIN_DETECTION
&CmndPowerDelta, &CmndPowerLow, &CmndPowerHigh, &CmndVoltageLow, &CmndVoltageHigh, &CmndCurrentLow, &CmndCurrentHigh,
@@ -547,14 +548,12 @@ void EnergyEverySecond(void)
* Commands
\*********************************************************************************************/
-void EnergyCommandCalResponse(uint32_t nvalue)
-{
+void EnergyCommandCalResponse(uint32_t nvalue) {
snprintf_P(XdrvMailbox.command, CMDSZ, PSTR("%sCal"), XdrvMailbox.command);
ResponseCmndNumber(nvalue);
}
-void CmndEnergyReset(void)
-{
+void CmndEnergyReset(void) {
uint32_t values[2] = { 0 };
uint32_t params = ParseParameters(2, values);
values[0] *= 100;
@@ -641,8 +640,7 @@ void CmndEnergyReset(void)
Settings.flag2.energy_resolution, &return2_kWhtotal);
}
-void CmndTariff(void)
-{
+void CmndTariff(void) {
// Tariff1 22:00,23:00 - Tariff1 start hour for Standard Time and Daylight Savings Time
// Tariff2 6:00,7:00 - Tariff2 start hour for Standard Time and Daylight Savings Time
// Tariffx 1320, 1380 = minutes and also 22:00, 23:00
@@ -686,11 +684,9 @@ void CmndTariff(void)
GetStateText(Settings.flag3.energy_weekend)); // CMND_TARIFF
}
-void CmndPowerCal(void)
-{
+void CmndPowerCal(void) {
Energy.command_code = CMND_POWERCAL;
if (XnrgCall(FUNC_COMMAND)) { // microseconds
-// if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
if (XdrvMailbox.payload > 999) {
Settings.energy_power_calibration = XdrvMailbox.payload;
}
@@ -698,11 +694,9 @@ void CmndPowerCal(void)
}
}
-void CmndVoltageCal(void)
-{
+void CmndVoltageCal(void) {
Energy.command_code = CMND_VOLTAGECAL;
if (XnrgCall(FUNC_COMMAND)) { // microseconds
-// if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
if (XdrvMailbox.payload > 999) {
Settings.energy_voltage_calibration = XdrvMailbox.payload;
}
@@ -710,11 +704,9 @@ void CmndVoltageCal(void)
}
}
-void CmndCurrentCal(void)
-{
+void CmndCurrentCal(void) {
Energy.command_code = CMND_CURRENTCAL;
if (XnrgCall(FUNC_COMMAND)) { // microseconds
-// if ((XdrvMailbox.payload > 999) && (XdrvMailbox.payload < 32001)) {
if (XdrvMailbox.payload > 999) {
Settings.energy_current_calibration = XdrvMailbox.payload;
}
@@ -722,40 +714,45 @@ void CmndCurrentCal(void)
}
}
-void CmndPowerSet(void)
-{
+void CmndFrequencyCal(void) {
+ Energy.command_code = CMND_FREQUENCYCAL;
+ if (XnrgCall(FUNC_COMMAND)) { // microseconds
+ if (XdrvMailbox.payload > 999) {
+ Settings.energy_frequency_calibration = XdrvMailbox.payload;
+ }
+ ResponseCmndNumber(Settings.energy_frequency_calibration);
+ }
+}
+
+void CmndPowerSet(void) {
Energy.command_code = CMND_POWERSET;
if (XnrgCall(FUNC_COMMAND)) { // Watt
EnergyCommandCalResponse(Settings.energy_power_calibration);
}
}
-void CmndVoltageSet(void)
-{
+void CmndVoltageSet(void) {
Energy.command_code = CMND_VOLTAGESET;
if (XnrgCall(FUNC_COMMAND)) { // Volt
EnergyCommandCalResponse(Settings.energy_voltage_calibration);
}
}
-void CmndCurrentSet(void)
-{
+void CmndCurrentSet(void) {
Energy.command_code = CMND_CURRENTSET;
if (XnrgCall(FUNC_COMMAND)) { // milliAmpere
EnergyCommandCalResponse(Settings.energy_current_calibration);
}
}
-void CmndFrequencySet(void)
-{
+void CmndFrequencySet(void) {
Energy.command_code = CMND_FREQUENCYSET;
if (XnrgCall(FUNC_COMMAND)) { // Hz
EnergyCommandCalResponse(Settings.energy_frequency_calibration);
}
}
-void CmndModuleAddress(void)
-{
+void CmndModuleAddress(void) {
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 4) && (1 == Energy.phase_count)) {
Energy.command_code = CMND_MODULEADDRESS;
if (XnrgCall(FUNC_COMMAND)) { // Module address
@@ -765,8 +762,7 @@ void CmndModuleAddress(void)
}
#ifdef USE_ENERGY_MARGIN_DETECTION
-void CmndPowerDelta(void)
-{
+void CmndPowerDelta(void) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32000)) {
Settings.energy_power_delta[XdrvMailbox.index -1] = XdrvMailbox.payload;
@@ -775,48 +771,42 @@ void CmndPowerDelta(void)
}
}
-void CmndPowerLow(void)
-{
+void CmndPowerLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_min_power = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_min_power);
}
-void CmndPowerHigh(void)
-{
+void CmndPowerHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power);
}
-void CmndVoltageLow(void)
-{
+void CmndVoltageLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
Settings.energy_min_voltage = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_min_voltage);
}
-void CmndVoltageHigh(void)
-{
+void CmndVoltageHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 501)) {
Settings.energy_max_voltage = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_voltage);
}
-void CmndCurrentLow(void)
-{
+void CmndCurrentLow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
Settings.energy_min_current = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_min_current);
}
-void CmndCurrentHigh(void)
-{
+void CmndCurrentHigh(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 16001)) {
Settings.energy_max_current = XdrvMailbox.payload;
}
@@ -824,56 +814,49 @@ void CmndCurrentHigh(void)
}
#ifdef USE_ENERGY_POWER_LIMIT
-void CmndMaxPower(void)
-{
+void CmndMaxPower(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_limit);
}
-void CmndMaxPowerHold(void)
-{
+void CmndMaxPowerHold(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit_hold = (1 == XdrvMailbox.payload) ? MAX_POWER_HOLD : XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_limit_hold);
}
-void CmndMaxPowerWindow(void)
-{
+void CmndMaxPowerWindow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_limit_window = (1 == XdrvMailbox.payload) ? MAX_POWER_WINDOW : XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_limit_window);
}
-void CmndSafePower(void)
-{
+void CmndSafePower(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_safe_limit = XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_safe_limit);
}
-void CmndSafePowerHold(void)
-{
+void CmndSafePowerHold(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_power_safe_limit_hold = (1 == XdrvMailbox.payload) ? SAFE_POWER_HOLD : XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_safe_limit_hold);
}
-void CmndSafePowerWindow(void)
-{
+void CmndSafePowerWindow(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 1440)) {
Settings.energy_max_power_safe_limit_window = (1 == XdrvMailbox.payload) ? SAFE_POWER_WINDOW : XdrvMailbox.payload;
}
ResponseCmndNumber(Settings.energy_max_power_safe_limit_window);
}
-void CmndMaxEnergy(void)
-{
+void CmndMaxEnergy(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 3601)) {
Settings.energy_max_energy = XdrvMailbox.payload;
Energy.max_energy_state = 3;
@@ -881,8 +864,7 @@ void CmndMaxEnergy(void)
ResponseCmndNumber(Settings.energy_max_energy);
}
-void CmndMaxEnergyStart(void)
-{
+void CmndMaxEnergyStart(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 24)) {
Settings.energy_max_energy_start = XdrvMailbox.payload;
}
diff --git a/tasmota/xnrg_19_cse7761.ino b/tasmota/xnrg_19_cse7761.ino
index 8c7b175a4..77237dda7 100644
--- a/tasmota/xnrg_19_cse7761.ino
+++ b/tasmota/xnrg_19_cse7761.ino
@@ -34,29 +34,31 @@
#define CSE7761_UREF 42563 // RmsUc
#define CSE7761_IREF 52241 // RmsIAC
#define CSE7761_PREF 44513 // PowerPAC
+#define CSE7761_FREF 3579545 // System clock (3.579545MHz) as used in frequency calculation
-#define CSE7761_REG_SYSCON 0x00 // System Control Register
-#define CSE7761_REG_EMUCON 0x01 // Metering control register
-#define CSE7761_REG_EMUCON2 0x13 // Metering control register 2
+#define CSE7761_REG_SYSCON 0x00 // (2) System Control Register (0x0A04)
+#define CSE7761_REG_EMUCON 0x01 // (2) Metering control register (0x0000)
+#define CSE7761_REG_EMUCON2 0x13 // (2) Metering control register 2 (0x0001)
-#define CSE7761_REG_UFREQ 0x23 // Voltage Frequency Register
-#define CSE7761_REG_RMSIA 0x24 // The effective value of channel A current
-#define CSE7761_REG_RMSIB 0x25 // The effective value of channel B current
-#define CSE7761_REG_RMSU 0x26 // Voltage RMS
-#define CSE7761_REG_POWERPA 0x2C // Channel A active power, update rate 27.2Hz
-#define CSE7761_REG_POWERPB 0x2D // Channel B active power, update rate 27.2Hz
-#define CSE7761_REG_SYSSTATUS 0x43 // System status register
+#define CSE7761_REG_UFREQ 0x23 // (2) Voltage Frequency (0x0000)
+#define CSE7761_REG_RMSIA 0x24 // (3) The effective value of channel A current (0x000000)
+#define CSE7761_REG_RMSIB 0x25 // (3) The effective value of channel B current (0x000000)
+#define CSE7761_REG_RMSU 0x26 // (3) Voltage RMS (0x000000)
+#define CSE7761_REG_POWERFACTOR 0x27 // (3) Power factor register, select by command: channel A Power factor or channel B power factor (0x7FFFFF)
+#define CSE7761_REG_POWERPA 0x2C // (4) Channel A active power, update rate 27.2Hz (0x00000000)
+#define CSE7761_REG_POWERPB 0x2D // (4) Channel B active power, update rate 27.2Hz (0x00000000)
+#define CSE7761_REG_SYSSTATUS 0x43 // (1) System status register
-#define CSE7761_REG_COEFFOFFSET 0x6E // Coefficient checksum offset (0xFFFF)
-#define CSE7761_REG_COEFFCHKSUM 0x6F // Coefficient checksum
-#define CSE7761_REG_RMSIAC 0x70 // Channel A effective current conversion coefficient
-#define CSE7761_REG_RMSIBC 0x71 // Channel B effective current conversion coefficient
-#define CSE7761_REG_RMSUC 0x72 // Effective voltage conversion coefficient
-#define CSE7761_REG_POWERPAC 0x73 // Channel A active power conversion coefficient
-#define CSE7761_REG_POWERPBC 0x74 // Channel B active power conversion coefficient
-#define CSE7761_REG_POWERSC 0x75 // Apparent power conversion coefficient
-#define CSE7761_REG_ENERGYAC 0x76 // Channel A energy conversion coefficient
-#define CSE7761_REG_ENERGYBC 0x77 // Channel B energy conversion coefficient
+#define CSE7761_REG_COEFFOFFSET 0x6E // (2) Coefficient checksum offset (0xFFFF)
+#define CSE7761_REG_COEFFCHKSUM 0x6F // (2) Coefficient checksum
+#define CSE7761_REG_RMSIAC 0x70 // (2) Channel A effective current conversion coefficient
+#define CSE7761_REG_RMSIBC 0x71 // (2) Channel B effective current conversion coefficient
+#define CSE7761_REG_RMSUC 0x72 // (2) Effective voltage conversion coefficient
+#define CSE7761_REG_POWERPAC 0x73 // (2) Channel A active power conversion coefficient
+#define CSE7761_REG_POWERPBC 0x74 // (2) Channel B active power conversion coefficient
+#define CSE7761_REG_POWERSC 0x75 // (2) Apparent power conversion coefficient
+#define CSE7761_REG_ENERGYAC 0x76 // (2) Channel A energy conversion coefficient
+#define CSE7761_REG_ENERGYBC 0x77 // (2) Channel B energy conversion coefficient
#define CSE7761_SPECIAL_COMMAND 0xEA // Start special command
#define CSE7761_CMD_RESET 0x96 // Reset command, after receiving the command, the chip resets
@@ -76,6 +78,7 @@ enum CSE7761 { RmsIAC, RmsIBC, RmsUC, PowerPAC, PowerPBC, PowerSC, EnergyAC, Ene
TasmotaSerial *Cse7761Serial = nullptr;
struct {
+ uint32_t frequency = 0;
uint32_t voltage_rms = 0;
uint32_t current_rms[2] = { 0 };
uint32_t energy[2] = { 0 };
@@ -114,7 +117,7 @@ void Cse7761Write(uint32_t reg, uint32_t data) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: Tx %*_H"), len, buffer);
}
-uint32_t Cse7761ReadOnce(uint32_t log_level, uint32_t reg, uint32_t size) {
+bool Cse7761ReadOnce(uint32_t log_level, uint32_t reg, uint32_t size, uint32_t* value) {
while (Cse7761Serial->available()) { Cse7761Serial->read(); }
Cse7761Write(reg, 0);
@@ -133,12 +136,12 @@ uint32_t Cse7761ReadOnce(uint32_t log_level, uint32_t reg, uint32_t size) {
if (!rcvd) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: Rx none"));
- return 0;
+ return false;
}
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: Rx %*_H"), rcvd, buffer);
if (rcvd > 5) {
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: Rx overflow"));
- return 0;
+ return false;
}
rcvd--;
@@ -151,25 +154,27 @@ uint32_t Cse7761ReadOnce(uint32_t log_level, uint32_t reg, uint32_t size) {
crc = ~crc;
if (crc != buffer[rcvd]) {
AddLog(log_level, PSTR("C61: Rx %*_H, CRC error %02X"), rcvd +1, buffer, crc);
- return 1;
+ return false;
}
- return result;
+ *value = result;
+ return true;
}
uint32_t Cse7761Read(uint32_t reg, uint32_t size) {
- uint32_t value = 0;
- uint32_t retry = 3;
- while ((value < 2) && (retry)) {
+ bool result = false; // Start loop
+ uint32_t retry = 3; // Retry up to three times
+ uint32_t value = 0; // Default no value
+ while (!result && retry) {
retry--;
- value = Cse7761ReadOnce((retry) ? LOG_LEVEL_DEBUG_MORE : LOG_LEVEL_DEBUG, reg, size);
+ result = Cse7761ReadOnce((retry) ? LOG_LEVEL_DEBUG_MORE : LOG_LEVEL_DEBUG, reg, size, &value);
}
return value;
}
uint32_t Cse7761ReadFallback(uint32_t reg, uint32_t prev, uint32_t size) {
uint32_t value = Cse7761Read(reg, size);
- if (1 == value) { // CRC Error so use previous value read
+ if (!value) { // Error so use previous value read
value = prev;
}
return value;
@@ -202,10 +207,15 @@ bool Cse7761ChipInit(void) {
// CSE7761Data.coefficient[PowerPBC] = 0xADD7;
}
if (HLW_PREF_PULSE == Settings.energy_power_calibration) {
+ Settings.energy_frequency_calibration = CSE7761_FREF;
Settings.energy_voltage_calibration = Cse7761Ref(RmsUC);
Settings.energy_current_calibration = Cse7761Ref(RmsIAC);
Settings.energy_power_calibration = Cse7761Ref(PowerPAC);
}
+ // Just to fix intermediate users
+ if (Settings.energy_frequency_calibration < CSE7761_FREF / 2) {
+ Settings.energy_frequency_calibration = CSE7761_FREF;
+ }
Cse7761Write(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
@@ -320,7 +330,7 @@ bool Cse7761ChipInit(void) {
=1, turn on the power factor output function (Sonoff Dual R3 Pow)
=0, turn off the power factor output function
5 WaveEN Waveform data, instantaneous data output enable signal
- =1, turn on the waveform data output function
+ =1, turn on the waveform data output function (Tasmota add frequency)
=0, turn off the waveform data output function (Sonoff Dual R3 Pow)
4 SAGEN Voltage drop detection enable signal, WaveEN=1 must be configured first
=1, turn on the voltage drop detection function
@@ -329,14 +339,15 @@ bool Cse7761ChipInit(void) {
=1, turn on the overvoltage, overcurrent, and overload detection functions
=0, turn off the overvoltage, overcurrent, and overload detection functions (Sonoff Dual R3 Pow)
2 ZxEN Zero-crossing detection, phase angle, voltage frequency measurement enable signal
- =1, turn on the zero-crossing detection, phase angle, and voltage frequency measurement functions
+ =1, turn on the zero-crossing detection, phase angle, and voltage frequency measurement functions (Tasmota add frequency)
=0, disable zero-crossing detection, phase angle, voltage frequency measurement functions (Sonoff Dual R3 Pow)
1 PeakEN Peak detect enable signal
=1, turn on the peak detection function
=0, turn off the peak detection function (Sonoff Dual R3 Pow)
0 NC Default is 1
*/
- Cse7761Write(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
+// Cse7761Write(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1); // Sonoff Dual R3 Pow
+ Cse7761Write(CSE7761_REG_EMUCON2 | 0x80, 0x0FE5); // Tasmota add Frequency
} else {
AddLog(LOG_LEVEL_DEBUG, PSTR("C61: Write failed"));
return false;
@@ -354,6 +365,12 @@ void Cse7761GetData(void) {
#endif
CSE7761Data.voltage_rms = (value >= 0x800000) ? 0 : value;
+ value = Cse7761ReadFallback(CSE7761_REG_UFREQ, CSE7761Data.frequency, 2);
+#ifdef CSE7761_SIMULATE
+ value = 8948; // 49.99Hz
+#endif
+ CSE7761Data.frequency = (value >= 0x8000) ? 0 : value;
+
value = Cse7761ReadFallback(CSE7761_REG_RMSIA, CSE7761Data.current_rms[0], 3);
#ifdef CSE7761_SIMULATE
value = 455;
@@ -376,8 +393,8 @@ void Cse7761GetData(void) {
#endif
CSE7761Data.active_power[1] = (0 == CSE7761Data.current_rms[1]) ? 0 : (value & 0x80000000) ? (~value) + 1 : value;
- AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: U%d, I%d/%d, P%d/%d"),
- CSE7761Data.voltage_rms,
+ AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("C61: F%d, U%d, I%d/%d, P%d/%d"),
+ CSE7761Data.frequency, CSE7761Data.voltage_rms,
CSE7761Data.current_rms[0], CSE7761Data.current_rms[1],
CSE7761Data.active_power[0], CSE7761Data.active_power[1]);
@@ -385,6 +402,7 @@ void Cse7761GetData(void) {
// Voltage = RmsU * RmsUC * 10 / 0x400000
// Energy.voltage[0] = (float)(((uint64_t)CSE7761Data.voltage_rms * CSE7761Data.coefficient[RmsUC] * 10) >> 22) / 1000; // V
Energy.voltage[0] = ((float)CSE7761Data.voltage_rms / Settings.energy_voltage_calibration); // V
+ Energy.frequency[0] = (CSE7761Data.frequency) ? ((float)Settings.energy_frequency_calibration / 8 / CSE7761Data.frequency) : 0; // Hz
for (uint32_t channel = 0; channel < 2; channel++) {
Energy.data_valid[channel] = 0;
@@ -470,6 +488,7 @@ void Cse7761DrvInit(void) {
CSE7761Data.init = 4; // Init setup steps
Energy.phase_count = 2; // Handle two channels as two phases
Energy.voltage_common = true; // Use common voltage
+ Energy.frequency_common = true; // Use common frequency
TasmotaGlobal.energy_driver = XNRG_19;
}
}
@@ -492,6 +511,10 @@ bool Cse7761Command(void) {
if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = Cse7761Ref(RmsIAC); }
// Service in xdrv_03_energy.ino
}
+ else if (CMND_FREQUENCYCAL == Energy.command_code) {
+ if (1 == XdrvMailbox.payload) { XdrvMailbox.payload = CSE7761_FREF; }
+ // Service in xdrv_03_energy.ino
+ }
else if (CMND_POWERSET == Energy.command_code) {
if (XdrvMailbox.data_len && CSE7761Data.active_power[channel]) {
if ((value > 100) && (value < 200000)) { // Between 1W and 2000W
@@ -513,6 +536,13 @@ bool Cse7761Command(void) {
}
}
}
+ else if (CMND_FREQUENCYSET == Energy.command_code) {
+ if (XdrvMailbox.data_len && CSE7761Data.frequency) {
+ if ((value > 4500) && (value < 6500)) { // Between 45.00Hz and 65.00Hz
+ Settings.energy_frequency_calibration = (CSE7761Data.frequency * 8 * value) / 100;
+ }
+ }
+ }
else serviced = false; // Unknown command
return serviced;
From aacaf7770782f601df9367df6e9ae806e0caad20 Mon Sep 17 00:00:00 2001
From: Stephan Hadinger
Date: Sun, 21 Mar 2021 19:45:32 +0100
Subject: [PATCH 25/35] Berry fix rules
---
tasmota/xdrv_52_9_berry.ino | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tasmota/xdrv_52_9_berry.ino b/tasmota/xdrv_52_9_berry.ino
index df2ba5b6b..1a3c4fa81 100644
--- a/tasmota/xdrv_52_9_berry.ino
+++ b/tasmota/xdrv_52_9_berry.ino
@@ -84,7 +84,7 @@ bool callBerryRule(void) {
berry.rules_busy = true;
char * json_event = TasmotaGlobal.mqtt_data;
bool serviced = false;
- serviced = callBerryEventDispatcher(PSTR("exec_rules"), nullptr, 0, TasmotaGlobal.mqtt_data);
+ serviced = callBerryEventDispatcher(PSTR("rule"), nullptr, 0, TasmotaGlobal.mqtt_data);
berry.rules_busy = false;
return serviced; // TODO event not handled
}
From acd28f63082e4d96a1296ab241b6c384f64a0b4a Mon Sep 17 00:00:00 2001
From: oponyx <78806035+oponyx@users.noreply.github.com>
Date: Mon, 22 Mar 2021 01:45:46 +0100
Subject: [PATCH 26/35] Update xdrv_20_hue.ino
---
tasmota/xdrv_20_hue.ino | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino
index ac48628df..5bb442bbe 100644
--- a/tasmota/xdrv_20_hue.ino
+++ b/tasmota/xdrv_20_hue.ino
@@ -411,8 +411,8 @@ String GetHueDeviceId(uint16_t id)
{
String deviceid = WiFi.macAddress();
deviceid += F(":00:11-");
- if(id<9) deviceid += F("0");
- deviceid += String(id);
+ if(id<0x10) deviceid += F("0");
+ deviceid += String(id,HEX);
deviceid.toLowerCase();
return deviceid; // 5c:cf:7f:13:9f:3d:00:11-01
}
From 64c8c8899240b4df2dcd74edf32da8d260e452e4 Mon Sep 17 00:00:00 2001
From: kylehase
Date: Mon, 22 Mar 2021 12:50:26 +0900
Subject: [PATCH 27/35] Update xdrv_05_irremote.ino
Reduce IR_TIME_AVOID_DUPLICATE to 50ms which was fixed in xdrv_05_irremote_full.ino in PR #9969 but not here.
Converted IR_TIME_AVOID_DUPLICATE from const to #define to enable override in the config override file.
---
tasmota/xdrv_05_irremote.ino | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tasmota/xdrv_05_irremote.ino b/tasmota/xdrv_05_irremote.ino
index 0cacb2161..0da389171 100644
--- a/tasmota/xdrv_05_irremote.ino
+++ b/tasmota/xdrv_05_irremote.ino
@@ -176,7 +176,10 @@ void IrSendInit(void)
\*********************************************************************************************/
const bool IR_RCV_SAVE_BUFFER = false; // false = do not use buffer, true = use buffer for decoding
-const uint32_t IR_TIME_AVOID_DUPLICATE = 500; // Milliseconds
+
+#ifndef IR_TIME_AVOID_DUPLICATE
+#define IR_TIME_AVOID_DUPLICATE = 50 // Milliseconds
+#endif // IR_TIME_AVOID_DUPLICATE
#include
From 60613e504141400405925b0d392ddf141ae3a856 Mon Sep 17 00:00:00 2001
From: kylehase
Date: Mon, 22 Mar 2021 13:03:44 +0900
Subject: [PATCH 28/35] Update xdrv_05_irremote_full.ino
Convert IR_TIME_AVOID_DUPLICATE from const to #define to enable override in the config override file.
---
tasmota/xdrv_05_irremote_full.ino | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/tasmota/xdrv_05_irremote_full.ino b/tasmota/xdrv_05_irremote_full.ino
index 66d0e17d4..b68cc5a87 100644
--- a/tasmota/xdrv_05_irremote_full.ino
+++ b/tasmota/xdrv_05_irremote_full.ino
@@ -167,7 +167,10 @@ uint64_t reverseBitsInBytes64(uint64_t b) {
\*********************************************************************************************/
const bool IR_FULL_RCV_SAVE_BUFFER = false; // false = do not use buffer, true = use buffer for decoding
-const uint32_t IR_TIME_AVOID_DUPLICATE = 50; // Milliseconds
+
+#ifndef IR_TIME_AVOID_DUPLICATE
+#define IR_TIME_AVOID_DUPLICATE = 50 // Milliseconds
+#endif // IR_TIME_AVOID_DUPLICATE
// Below is from IRrecvDumpV2.ino
// As this program is a special purpose capture/decoder, let us use a larger
From 0614e54363b5beb841cfa2e931ecf1ddb4f48c75 Mon Sep 17 00:00:00 2001
From: kylehase
Date: Mon, 22 Mar 2021 13:25:01 +0900
Subject: [PATCH 29/35] Update xdrv_05_irremote_full.ino
---
tasmota/xdrv_05_irremote_full.ino | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tasmota/xdrv_05_irremote_full.ino b/tasmota/xdrv_05_irremote_full.ino
index b68cc5a87..00b6cf7bd 100644
--- a/tasmota/xdrv_05_irremote_full.ino
+++ b/tasmota/xdrv_05_irremote_full.ino
@@ -169,7 +169,7 @@ uint64_t reverseBitsInBytes64(uint64_t b) {
const bool IR_FULL_RCV_SAVE_BUFFER = false; // false = do not use buffer, true = use buffer for decoding
#ifndef IR_TIME_AVOID_DUPLICATE
-#define IR_TIME_AVOID_DUPLICATE = 50 // Milliseconds
+#define IR_TIME_AVOID_DUPLICATE 50 // Milliseconds
#endif // IR_TIME_AVOID_DUPLICATE
// Below is from IRrecvDumpV2.ino
From a0c6d4f3001fc608afd1a9c1571405d82c3e5d4c Mon Sep 17 00:00:00 2001
From: kylehase
Date: Mon, 22 Mar 2021 13:25:33 +0900
Subject: [PATCH 30/35] Update xdrv_05_irremote.ino
---
tasmota/xdrv_05_irremote.ino | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tasmota/xdrv_05_irremote.ino b/tasmota/xdrv_05_irremote.ino
index 0da389171..fa4831746 100644
--- a/tasmota/xdrv_05_irremote.ino
+++ b/tasmota/xdrv_05_irremote.ino
@@ -178,7 +178,7 @@ void IrSendInit(void)
const bool IR_RCV_SAVE_BUFFER = false; // false = do not use buffer, true = use buffer for decoding
#ifndef IR_TIME_AVOID_DUPLICATE
-#define IR_TIME_AVOID_DUPLICATE = 50 // Milliseconds
+#define IR_TIME_AVOID_DUPLICATE 50 // Milliseconds
#endif // IR_TIME_AVOID_DUPLICATE
#include
From 633489a91e562019e6f61f86950f58e06cc584fb Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Mon, 22 Mar 2021 12:34:52 +0100
Subject: [PATCH 31/35] Add commands ``DisplayType`` and ``DisplayInvert``
Add commands ``DisplayType`` to select sub-modules where implemented and ``DisplayInvert`` to select inverted display where implemented
---
CHANGELOG.md | 1 +
RELEASENOTES.md | 4 +
tasmota/settings.h | 10 +-
tasmota/xdrv_13_display.ino | 413 +++++++++++++++++-------------------
tasmota/xdsp_04_ili9341.ino | 12 +-
5 files changed, 206 insertions(+), 234 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d840a1395..dbc59b21b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
## [9.3.1.2]
### Added
- Commands ``MqttKeepAlive 1..100`` to set Mqtt Keep Alive timer (default 30) and ``MqttTimeout 1..100`` to set Mqtt Socket Timeout (default 4) (#5341)
+- Commands ``DisplayType`` to select sub-modules where implemented and ``DisplayInvert`` to select inverted display where implemented
- Support for TM1638 seven segment display by Ajith Vasudevan (#11031)
### Changed
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 19e7f50c2..b94f603c8 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -82,16 +82,20 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota
### Added
- Commands ``MqttKeepAlive 1..100`` to set Mqtt Keep Alive timer (default 30) and ``MqttTimeout 1..100`` to set Mqtt Socket Timeout (default 4) [#5341](https://github.com/arendst/Tasmota/issues/5341)
- Command ``Sensor80 1 <0..7>`` to control MFRC522 RFID antenna gain from 18dB (0) to 48dB (7) [#11073](https://github.com/arendst/Tasmota/issues/11073)
+- Commands ``DisplayType`` to select sub-modules where implemented and ``DisplayInvert`` to select inverted display where implemented
- Support for SML VBUS [#11125](https://github.com/arendst/Tasmota/issues/11125)
- Support for NEC and OPTOMA LCD/DLP Projector serial power control by Jan Bubík [#11145](https://github.com/arendst/Tasmota/issues/11145)
- Support for XPT2046 touch screen digitizer on ILI9341 display by nonix [#11159](https://github.com/arendst/Tasmota/issues/11159)
- Support for zigbee lumi.sensor_wleak [#11200](https://github.com/arendst/Tasmota/issues/11200)
- Support for CSE7761 energy monitor as used in ESP32 based Sonoff Dual R3 Pow [#10793](https://github.com/arendst/Tasmota/issues/10793)
- Support for TM1638 seven segment display by Ajith Vasudevan [#11031](https://github.com/arendst/Tasmota/issues/11031)
+- Support for MPU6886 on primary or secondary I2C bus
- Allow MCP230xx pinmode from output to input [#11104](https://github.com/arendst/Tasmota/issues/11104)
- Berry improvements [#11163](https://github.com/arendst/Tasmota/issues/11163)
- Extent compile time SetOptions support [#11204](https://github.com/arendst/Tasmota/issues/11204)
- ESP32 Extent BLE [#11212](https://github.com/arendst/Tasmota/issues/11212)
+- ESP32 support for WS2812 hardware driver via RMT or I2S
+- ESP32 support for secondary I2C controller
### Changed
- TasmotaSerial library from v3.2.0 to v3.3.0
diff --git a/tasmota/settings.h b/tasmota/settings.h
index 05ab83922..804f47835 100644
--- a/tasmota/settings.h
+++ b/tasmota/settings.h
@@ -149,7 +149,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t mqtt_state_retain : 1; // bit 7 (v9.3.0.1) - CMND_STATERETAIN
uint32_t mqtt_info_retain : 1; // bit 8 (v9.3.0.1) - CMND_INFORETAIN
uint32_t wiegand_hex_output : 1; // bit 9 (v9.3.1.1) - SetOption123 - (Wiegand) switch tag number output to hex format (1)
- uint32_t wiegand_keypad_to_tag : 1; // bit 10 (v9.3.1.1) - SetOption124 - (Wiegand) send key pad stroke as single char (0) or one tag (ending char #) (1)
+ uint32_t wiegand_keypad_to_tag : 1; // bit 10 (v9.3.1.1) - SetOption124 - (Wiegand) send key pad stroke as single char (0) or one tag (ending char #) (1)
uint32_t zigbee_hide_bridge_topic : 1; // bit 11 (v9.3.1.1) - SetOption125 - (Zigbee) Hide bridge topic from zigbee topic (use with SetOption89) (1)
uint32_t spare12 : 1; // bit 12
uint32_t spare13 : 1; // bit 13
@@ -330,12 +330,12 @@ typedef struct {
typedef union {
uint8_t data;
struct {
- uint8_t ilimode : 3;
- uint8_t Invert : 1;
- uint8_t spare2 : 1;
- uint8_t spare3 : 1;
+ uint8_t type : 3;
+ uint8_t invert : 1;
uint8_t spare4 : 1;
uint8_t spare5 : 1;
+ uint8_t spare6 : 1;
+ uint8_t spare7 : 1;
};
} DisplayOptions;
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index a8f50d735..e87e34100 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -54,22 +54,24 @@ const uint8_t DISPLAY_LOG_ROWS = 32; // Number of lines in display log
#define D_CMND_DISP_DIMMER "Dimmer"
#define D_CMND_DISP_MODE "Mode"
#define D_CMND_DISP_MODEL "Model"
+#define D_CMND_DISP_TYPE "Type"
#define D_CMND_DISP_REFRESH "Refresh"
#define D_CMND_DISP_ROWS "Rows"
#define D_CMND_DISP_SIZE "Size"
#define D_CMND_DISP_FONT "Font"
#define D_CMND_DISP_ROTATE "Rotate"
-#define D_CMND_DISP_TEXT "Text"
+#define D_CMND_DISP_INVERT "Invert"
#define D_CMND_DISP_WIDTH "Width"
#define D_CMND_DISP_HEIGHT "Height"
#define D_CMND_DISP_BLINKRATE "Blinkrate"
#define D_CMND_DISP_BATCH "Batch"
+#define D_CMND_DISP_TEXT "Text"
+
#define D_CMND_DISP_CLEAR "Clear"
#define D_CMND_DISP_NUMBER "Number"
#define D_CMND_DISP_FLOAT "Float"
-#define D_CMND_DISP_NUMBERNC "NumberNC" // NC - "No Clear"
-#define D_CMND_DISP_FLOATNC "FloatNC" // NC - "No Clear"
-#define D_CMND_DISP_BRIGHTNESS "Brightness"
+#define D_CMND_DISP_NUMBERNC "NumberNC" // NC - "No Clear"
+#define D_CMND_DISP_FLOATNC "FloatNC" // NC - "No Clear"
#define D_CMND_DISP_RAW "Raw"
#define D_CMND_DISP_LEVEL "Level"
#define D_CMND_DISP_SEVENSEG_TEXT "SevensegText"
@@ -78,9 +80,6 @@ const uint8_t DISPLAY_LOG_ROWS = 32; // Number of lines in display log
#define D_CMND_DISP_CLOCK "Clock"
#define D_CMND_DISP_TEXTNC "TextNC" // NC - "No Clear"
#define D_CMND_DISP_SCROLLTEXT "ScrollText"
-#define D_CMND_DISP_ILIMODE "ILIMode"
-#define D_CMND_DISP_ILIINVERT "Invert"
-
enum XdspFunctions { FUNC_DISPLAY_INIT_DRIVER, FUNC_DISPLAY_INIT, FUNC_DISPLAY_EVERY_50_MSECOND, FUNC_DISPLAY_EVERY_SECOND,
FUNC_DISPLAY_MODEL, FUNC_DISPLAY_MODE, FUNC_DISPLAY_POWER,
@@ -101,29 +100,27 @@ enum XdspFunctions { FUNC_DISPLAY_INIT_DRIVER, FUNC_DISPLAY_INIT, FUNC_DISPLAY_E
enum DisplayInitModes { DISPLAY_INIT_MODE, DISPLAY_INIT_PARTIAL, DISPLAY_INIT_FULL };
const char kDisplayCommands[] PROGMEM = D_PRFX_DISPLAY "|" // Prefix
- "|" D_CMND_DISP_MODEL "|" D_CMND_DISP_WIDTH "|" D_CMND_DISP_HEIGHT "|" D_CMND_DISP_MODE "|" D_CMND_DISP_REFRESH "|"
- D_CMND_DISP_DIMMER "|" D_CMND_DISP_COLS "|" D_CMND_DISP_ROWS "|" D_CMND_DISP_SIZE "|" D_CMND_DISP_FONT "|"
- D_CMND_DISP_ROTATE "|" D_CMND_DISP_TEXT "|" D_CMND_DISP_ADDRESS "|" D_CMND_DISP_BLINKRATE "|"
+ "|" D_CMND_DISP_MODEL "|" D_CMND_DISP_TYPE "|" D_CMND_DISP_WIDTH "|" D_CMND_DISP_HEIGHT "|" D_CMND_DISP_MODE "|"
+ D_CMND_DISP_INVERT "|" D_CMND_DISP_REFRESH "|" D_CMND_DISP_DIMMER "|" D_CMND_DISP_COLS "|" D_CMND_DISP_ROWS "|"
+ D_CMND_DISP_SIZE "|" D_CMND_DISP_FONT "|" D_CMND_DISP_ROTATE "|" D_CMND_DISP_TEXT "|" D_CMND_DISP_ADDRESS "|" D_CMND_DISP_BLINKRATE "|"
#ifdef USE_UFILESYS
D_CMND_DISP_BATCH "|"
#endif
D_CMND_DISP_CLEAR "|" D_CMND_DISP_NUMBER "|" D_CMND_DISP_FLOAT "|" D_CMND_DISP_NUMBERNC "|" D_CMND_DISP_FLOATNC "|"
D_CMND_DISP_RAW "|" D_CMND_DISP_LEVEL "|" D_CMND_DISP_SEVENSEG_TEXT "|" D_CMND_DISP_SEVENSEG_TEXTNC "|"
- D_CMND_DISP_SCROLLDELAY "|" D_CMND_DISP_CLOCK "|" D_CMND_DISP_TEXTNC "|"
- D_CMND_DISP_SCROLLTEXT "|" D_CMND_DISP_ILIMODE "|" D_CMND_DISP_ILIINVERT
+ D_CMND_DISP_SCROLLDELAY "|" D_CMND_DISP_CLOCK "|" D_CMND_DISP_TEXTNC "|" D_CMND_DISP_SCROLLTEXT
;
void (* const DisplayCommand[])(void) PROGMEM = {
- &CmndDisplay, &CmndDisplayModel, &CmndDisplayWidth, &CmndDisplayHeight, &CmndDisplayMode, &CmndDisplayRefresh,
- &CmndDisplayDimmer, &CmndDisplayColumns, &CmndDisplayRows, &CmndDisplaySize, &CmndDisplayFont,
- &CmndDisplayRotate, &CmndDisplayText, &CmndDisplayAddress, &CmndDisplayBlinkrate,
+ &CmndDisplay, &CmndDisplayModel, &CmndDisplayType, &CmndDisplayWidth, &CmndDisplayHeight, &CmndDisplayMode,
+ &CmndDisplayInvert, &CmndDisplayRefresh, &CmndDisplayDimmer, &CmndDisplayColumns, &CmndDisplayRows,
+ &CmndDisplaySize, &CmndDisplayFont, &CmndDisplayRotate, &CmndDisplayText, &CmndDisplayAddress, &CmndDisplayBlinkrate,
#ifdef USE_UFILESYS
&CmndDisplayBatch,
#endif
&CmndDisplayClear, &CmndDisplayNumber, &CmndDisplayFloat, &CmndDisplayNumberNC, &CmndDisplayFloatNC,
&CmndDisplayRaw, &CmndDisplayLevel, &CmndDisplaySevensegText, &CmndDisplaySevensegTextNC,
- &CmndDisplayScrollDelay, &CmndDisplayClock, &CmndDisplayTextNC,
- &CmndDisplayScrollText, &CmndDisplayILIMOde , &CmndDisplayILIInvert
+ &CmndDisplayScrollDelay, &CmndDisplayClock, &CmndDisplayTextNC, &CmndDisplayScrollText
};
char *dsp_str;
@@ -1637,18 +1634,16 @@ void DisplaySetPower(void)
* Commands
\*********************************************************************************************/
-void CmndDisplay(void)
-{
- Response_P(PSTR("{\"" D_PRFX_DISPLAY "\":{\"" D_CMND_DISP_MODEL "\":%d,\"" D_CMND_DISP_WIDTH "\":%d,\"" D_CMND_DISP_HEIGHT "\":%d,\""
+void CmndDisplay(void) {
+ Response_P(PSTR("{\"" D_PRFX_DISPLAY "\":{\"" D_CMND_DISP_MODEL "\":%d,\"" D_CMND_DISP_TYPE "\":%d,\"" D_CMND_DISP_WIDTH "\":%d,\"" D_CMND_DISP_HEIGHT "\":%d,\""
D_CMND_DISP_MODE "\":%d,\"" D_CMND_DISP_DIMMER "\":%d,\"" D_CMND_DISP_SIZE "\":%d,\"" D_CMND_DISP_FONT "\":%d,\""
- D_CMND_DISP_ROTATE "\":%d,\"" D_CMND_DISP_REFRESH "\":%d,\"" D_CMND_DISP_COLS "\":[%d,%d],\"" D_CMND_DISP_ROWS "\":%d}}"),
- Settings.display_model, Settings.display_width, Settings.display_height,
+ D_CMND_DISP_ROTATE "\":%d,\"" D_CMND_DISP_INVERT "\":%d,\"" D_CMND_DISP_REFRESH "\":%d,\"" D_CMND_DISP_COLS "\":[%d,%d],\"" D_CMND_DISP_ROWS "\":%d}}"),
+ Settings.display_model, Settings.display_options.type, Settings.display_width, Settings.display_height,
Settings.display_mode, changeUIntScale(Settings.display_dimmer, 0, 15, 0, 100), Settings.display_size, Settings.display_font,
- Settings.display_rotate, Settings.display_refresh, Settings.display_cols[0], Settings.display_cols[1], Settings.display_rows);
+ Settings.display_rotate, Settings.display_options.invert, Settings.display_refresh, Settings.display_cols[0], Settings.display_cols[1], Settings.display_rows);
}
-void CmndDisplayModel(void)
-{
+void CmndDisplayModel(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < DISPLAY_MAX_DRIVERS)) {
uint32_t last_display_model = Settings.display_model;
Settings.display_model = XdrvMailbox.payload;
@@ -1661,8 +1656,15 @@ void CmndDisplayModel(void)
ResponseCmndNumber(Settings.display_model);
}
-void CmndDisplayWidth(void)
-{
+void CmndDisplayType(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 7)) {
+ Settings.display_options.type = XdrvMailbox.payload;
+ TasmotaGlobal.restart_flag = 2;
+ }
+ ResponseCmndNumber(Settings.display_options.type);
+}
+
+void CmndDisplayWidth(void) {
if (XdrvMailbox.payload > 0) {
if (XdrvMailbox.payload != Settings.display_width) {
Settings.display_width = XdrvMailbox.payload;
@@ -1672,8 +1674,7 @@ void CmndDisplayWidth(void)
ResponseCmndNumber(Settings.display_width);
}
-void CmndDisplayHeight(void)
-{
+void CmndDisplayHeight(void) {
if (XdrvMailbox.payload > 0) {
if (XdrvMailbox.payload != Settings.display_height) {
Settings.display_height = XdrvMailbox.payload;
@@ -1683,8 +1684,7 @@ void CmndDisplayHeight(void)
ResponseCmndNumber(Settings.display_height);
}
-void CmndDisplayMode(void)
-{
+void CmndDisplayMode(void) {
#ifdef USE_DISPLAY_MODES1TO5
/* Matrix / 7-segment LCD / Oled TFT
* 1 = Text up and time Time
@@ -1732,138 +1732,7 @@ void CmndDisplayDimmer(void) {
ResponseCmndNumber(changeUIntScale(Settings.display_dimmer, 0, 15, 0, 100));
}
-void CmndDisplayBlinkrate(void)
-{
- if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
-
- if (!renderer)
- XdspCall(FUNC_DISPLAY_BLINKRATE);
- }
- ResponseCmndNumber(XdrvMailbox.payload);
-}
-
-
-#ifdef USE_UFILESYS
-void CmndDisplayBatch(void) {
- if (XdrvMailbox.data_len > 0) {
- if (!Settings.display_mode) {
- Display_Text_From_File(XdrvMailbox.data);
- }
- ResponseCmndChar(XdrvMailbox.data);
- }
-}
-#endif
-
-
-void CmndDisplayClear(void)
-{
- if (!renderer)
- XdspCall(FUNC_DISPLAY_CLEAR);
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayNumber(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_NUMBER);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayFloat(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_FLOAT);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayNumberNC(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_NUMBERNC);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayFloatNC(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_FLOATNC);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayRaw(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_RAW);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayLevel(void)
-{
- bool result = false;
- if (!renderer) {
- result = XdspCall(FUNC_DISPLAY_LEVEL);
- }
- if(result) ResponseCmndNumber(XdrvMailbox.payload);
-}
-
-void CmndDisplaySevensegText(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_SEVENSEG_TEXT);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayTextNC(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_SEVENSEG_TEXTNC);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplaySevensegTextNC(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_SEVENSEG_TEXTNC);
- }
- ResponseCmndChar(XdrvMailbox.data);
-}
-
-void CmndDisplayScrollDelay(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_SCROLLDELAY);
- }
- ResponseCmndNumber(XdrvMailbox.payload);
-}
-
-void CmndDisplayClock(void)
-{
- if (!renderer) {
- XdspCall(FUNC_DISPLAY_CLOCK);
- }
- ResponseCmndNumber(XdrvMailbox.payload);
-}
-
-
-void CmndDisplayScrollText(void)
-{
- bool result = false;
- if (!renderer) {
- result = XdspCall(FUNC_DISPLAY_SCROLLTEXT);
- }
- if(result) ResponseCmndChar(XdrvMailbox.data);
-}
-
-
-void CmndDisplaySize(void)
-{
+void CmndDisplaySize(void) {
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 4)) {
Settings.display_size = XdrvMailbox.payload;
if (renderer) renderer->setTextSize(Settings.display_size);
@@ -1872,8 +1741,7 @@ void CmndDisplaySize(void)
ResponseCmndNumber(Settings.display_size);
}
-void CmndDisplayFont(void)
-{
+void CmndDisplayFont(void) {
if ((XdrvMailbox.payload >=0) && (XdrvMailbox.payload <= 4)) {
Settings.display_font = XdrvMailbox.payload;
if (renderer) renderer->setTextFont(Settings.display_font);
@@ -1882,27 +1750,7 @@ void CmndDisplayFont(void)
ResponseCmndNumber(Settings.display_font);
}
-
-void CmndDisplayILIMOde(void)
-{
- if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) {
- Settings.display_options.ilimode = XdrvMailbox.payload;
- TasmotaGlobal.restart_flag = 2;
- }
- ResponseCmndNumber(Settings.display_options.ilimode);
-}
-
-void CmndDisplayILIInvert(void)
-{
- if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
- Settings.display_options.Invert = XdrvMailbox.payload;
- if (renderer) renderer->invertDisplay(Settings.display_options.Invert);
- }
- ResponseCmndNumber(Settings.display_options.Invert);
-}
-
-void CmndDisplayRotate(void)
-{
+void CmndDisplayRotate(void) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 4)) {
if ((Settings.display_rotate) != XdrvMailbox.payload) {
/*
@@ -1926,8 +1774,77 @@ void CmndDisplayRotate(void)
ResponseCmndNumber(Settings.display_rotate);
}
-void CmndDisplayText(void)
-{
+void CmndDisplayInvert(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 1)) {
+ Settings.display_options.invert = XdrvMailbox.payload;
+ if (renderer) renderer->invertDisplay(Settings.display_options.invert);
+ }
+ ResponseCmndNumber(Settings.display_options.invert);
+}
+
+void CmndDisplayRefresh(void) {
+ if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) {
+ Settings.display_refresh = XdrvMailbox.payload;
+ }
+ ResponseCmndNumber(Settings.display_refresh);
+}
+
+void CmndDisplayColumns(void) {
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
+ if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_COLS)) {
+ Settings.display_cols[XdrvMailbox.index -1] = XdrvMailbox.payload;
+#ifdef USE_DISPLAY_MODES1TO5
+ if (1 == XdrvMailbox.index) {
+ DisplayLogBufferInit();
+ DisplayReAllocScreenBuffer();
+ }
+#endif // USE_DISPLAY_MODES1TO5
+ }
+ ResponseCmndIdxNumber(Settings.display_cols[XdrvMailbox.index -1]);
+ }
+}
+
+void CmndDisplayRows(void) {
+ if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_ROWS)) {
+ Settings.display_rows = XdrvMailbox.payload;
+#ifdef USE_DISPLAY_MODES1TO5
+ DisplayLogBufferInit();
+ DisplayReAllocScreenBuffer();
+#endif // USE_DISPLAY_MODES1TO5
+ }
+ ResponseCmndNumber(Settings.display_rows);
+}
+
+void CmndDisplayAddress(void) {
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
+ Settings.display_address[XdrvMailbox.index -1] = XdrvMailbox.payload;
+ }
+ ResponseCmndIdxNumber(Settings.display_address[XdrvMailbox.index -1]);
+ }
+}
+
+void CmndDisplayBlinkrate(void) {
+ if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 3)) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_BLINKRATE);
+ }
+ }
+ ResponseCmndNumber(XdrvMailbox.payload);
+}
+
+#ifdef USE_UFILESYS
+void CmndDisplayBatch(void) {
+ if (XdrvMailbox.data_len > 0) {
+ if (!Settings.display_mode) {
+ Display_Text_From_File(XdrvMailbox.data);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+ }
+}
+#endif
+
+void CmndDisplayText(void) {
if (disp_device && XdrvMailbox.data_len > 0) {
#ifndef USE_DISPLAY_MODES1TO5
DisplayText();
@@ -1944,50 +1861,100 @@ void CmndDisplayText(void)
}
}
-void CmndDisplayAddress(void)
-{
- if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 8)) {
- if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
- Settings.display_address[XdrvMailbox.index -1] = XdrvMailbox.payload;
- }
- ResponseCmndIdxNumber(Settings.display_address[XdrvMailbox.index -1]);
- }
+/*********************************************************************************************\
+ * Currently 7-segement specific - should have been handled by (extended) DisplayText command
+\*********************************************************************************************/
+
+void CmndDisplayClear(void) {
+ if (!renderer)
+ XdspCall(FUNC_DISPLAY_CLEAR);
+ ResponseCmndChar(XdrvMailbox.data);
}
-void CmndDisplayRefresh(void)
-{
- if ((XdrvMailbox.payload >= 1) && (XdrvMailbox.payload <= 7)) {
- Settings.display_refresh = XdrvMailbox.payload;
+void CmndDisplayNumber(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_NUMBER);
}
- ResponseCmndNumber(Settings.display_refresh);
+ ResponseCmndChar(XdrvMailbox.data);
}
-void CmndDisplayColumns(void)
-{
- if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
- if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_COLS)) {
- Settings.display_cols[XdrvMailbox.index -1] = XdrvMailbox.payload;
-#ifdef USE_DISPLAY_MODES1TO5
- if (1 == XdrvMailbox.index) {
- DisplayLogBufferInit();
- DisplayReAllocScreenBuffer();
- }
-#endif // USE_DISPLAY_MODES1TO5
- }
- ResponseCmndIdxNumber(Settings.display_cols[XdrvMailbox.index -1]);
+void CmndDisplayFloat(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_FLOAT);
}
+ ResponseCmndChar(XdrvMailbox.data);
}
-void CmndDisplayRows(void)
-{
- if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= DISPLAY_MAX_ROWS)) {
- Settings.display_rows = XdrvMailbox.payload;
-#ifdef USE_DISPLAY_MODES1TO5
- DisplayLogBufferInit();
- DisplayReAllocScreenBuffer();
-#endif // USE_DISPLAY_MODES1TO5
+void CmndDisplayNumberNC(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_NUMBERNC);
}
- ResponseCmndNumber(Settings.display_rows);
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplayFloatNC(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_FLOATNC);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplayRaw(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_RAW);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplayLevel(void) {
+ bool result = false;
+ if (!renderer) {
+ result = XdspCall(FUNC_DISPLAY_LEVEL);
+ }
+ if(result) ResponseCmndNumber(XdrvMailbox.payload);
+}
+
+void CmndDisplaySevensegText(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_SEVENSEG_TEXT);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplayTextNC(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_SEVENSEG_TEXTNC);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplaySevensegTextNC(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_SEVENSEG_TEXTNC);
+ }
+ ResponseCmndChar(XdrvMailbox.data);
+}
+
+void CmndDisplayScrollDelay(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_SCROLLDELAY);
+ }
+ ResponseCmndNumber(XdrvMailbox.payload);
+}
+
+void CmndDisplayClock(void) {
+ if (!renderer) {
+ XdspCall(FUNC_DISPLAY_CLOCK);
+ }
+ ResponseCmndNumber(XdrvMailbox.payload);
+}
+
+void CmndDisplayScrollText(void) {
+ bool result = false;
+ if (!renderer) {
+ result = XdspCall(FUNC_DISPLAY_SCROLLTEXT);
+ }
+ if(result) ResponseCmndChar(XdrvMailbox.data);
}
/*********************************************************************************************\
diff --git a/tasmota/xdsp_04_ili9341.ino b/tasmota/xdsp_04_ili9341.ino
index 737b9376f..60b2dd646 100644
--- a/tasmota/xdsp_04_ili9341.ino
+++ b/tasmota/xdsp_04_ili9341.ino
@@ -42,7 +42,7 @@ uint8_t ili9342_ctouch_counter = 0;
bool tft_init_done = false;
-//Settings.display_options.ilimode = ILIMODE_9341;
+//Settings.display_options.type = ILIMODE_9341;
/*********************************************************************************************/
@@ -65,8 +65,8 @@ void ILI9341_InitDriver()
// disable screen buffer
buffer = NULL;
- if (!Settings.display_options.ilimode || (Settings.display_options.ilimode >= ILIMODE_MAX)) {
- Settings.display_options.ilimode = ILIMODE_9341;
+ if (!Settings.display_options.type || (Settings.display_options.type >= ILIMODE_MAX)) {
+ Settings.display_options.type = ILIMODE_9341;
}
// default colors
@@ -77,11 +77,11 @@ void ILI9341_InitDriver()
if (TasmotaGlobal.soft_spi_enabled) {
// Init renderer, may use hardware spi, however we use SSPI defintion because SD card uses SPI definition (2 spi busses)
if (PinUsed(GPIO_SSPI_MOSI) && PinUsed(GPIO_SSPI_MISO) && PinUsed(GPIO_SSPI_SCLK)) {
- ili9341_2 = new ILI9341_2(Pin(GPIO_ILI9341_CS), Pin(GPIO_SSPI_MOSI), Pin(GPIO_SSPI_MISO), Pin(GPIO_SSPI_SCLK), Pin(GPIO_OLED_RESET), Pin(GPIO_ILI9341_DC), Pin(GPIO_BACKLIGHT), 2, Settings.display_options.ilimode & 3);
+ ili9341_2 = new ILI9341_2(Pin(GPIO_ILI9341_CS), Pin(GPIO_SSPI_MOSI), Pin(GPIO_SSPI_MISO), Pin(GPIO_SSPI_SCLK), Pin(GPIO_OLED_RESET), Pin(GPIO_ILI9341_DC), Pin(GPIO_BACKLIGHT), 2, Settings.display_options.type & 3);
}
} else if (TasmotaGlobal.spi_enabled) {
if (PinUsed(GPIO_ILI9341_DC)) {
- ili9341_2 = new ILI9341_2(Pin(GPIO_ILI9341_CS), Pin(GPIO_SPI_MOSI), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_CLK), Pin(GPIO_OLED_RESET), Pin(GPIO_ILI9341_DC), Pin(GPIO_BACKLIGHT), 1, Settings.display_options.ilimode & 3);
+ ili9341_2 = new ILI9341_2(Pin(GPIO_ILI9341_CS), Pin(GPIO_SPI_MOSI), Pin(GPIO_SPI_MISO), Pin(GPIO_SPI_CLK), Pin(GPIO_OLED_RESET), Pin(GPIO_ILI9341_DC), Pin(GPIO_BACKLIGHT), 1, Settings.display_options.type & 3);
}
}
@@ -101,7 +101,7 @@ void ILI9341_InitDriver()
renderer->setTextFont(2);
renderer->setTextSize(1);
renderer->setTextColor(ILI9341_WHITE, ILI9341_BLACK);
- renderer->DrawStringAt(50, (Settings.display_height/2)-12, (Settings.display_options.ilimode & 3)==ILIMODE_9341?"ILI9341 TFT!":"ILI9342 TFT!", ILI9341_WHITE, 0);
+ renderer->DrawStringAt(50, (Settings.display_height/2)-12, (Settings.display_options.type & 3)==ILIMODE_9341?"ILI9341 TFT!":"ILI9342 TFT!", ILI9341_WHITE, 0);
delay(1000);
#endif // SHOW_SPLASH
From 11beacf956450bb20b8498c96832a767a25db85f Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Mon, 22 Mar 2021 12:47:15 +0100
Subject: [PATCH 32/35] Add support for another variant of the 6-digit TM1637
display
Add support for another variant of the 6-digit TM1637 display module selected by command ``DisplayType 1`` (#11422)
---
tasmota/xdsp_15_tm1637.ino | 46 +++++++++++++++++++++++++++-----------
1 file changed, 33 insertions(+), 13 deletions(-)
diff --git a/tasmota/xdsp_15_tm1637.ino b/tasmota/xdsp_15_tm1637.ino
index e2c5ae8ea..1d55ec26f 100644
--- a/tasmota/xdsp_15_tm1637.ino
+++ b/tasmota/xdsp_15_tm1637.ino
@@ -53,7 +53,7 @@
CS hardware pin --> "MAX7219 CS"
CLK hardware pin --> "MAX7219 CLK"
- Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts,
+ Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts,
set the Display Model to 15 and Display Mode to 0
using the command "Backlog DisplayModel 15 ; DisplayMode 0"
@@ -61,7 +61,7 @@
display has, using the command "DisplayWidth 6".
After the ESP8266/ESP32 module restarts again, turn ON the display with the command "Power 1"
-
+
Now, the following "Display" commands can be used:
@@ -146,7 +146,7 @@
"DisplayClock 0" // turn off clock
-In addition, setting DisplayMode to 1 shows the time, setting it to 2 shows the date
+In addition, setting DisplayMode to 1 shows the time, setting it to 2 shows the date
and setting it to 3 alternates between time and date.
@@ -190,6 +190,7 @@ struct
uint8_t scroll_index = 0;
uint8_t iteration = 0;
uint8_t display_type = TM1637;
+ uint8_t digit_order[6] = { 0, 1, 2, 3, 4, 5 };
bool init_done = false;
bool scroll = false;
@@ -214,7 +215,9 @@ void TM1637Init(void)
if ((!Settings.display_width || Settings.display_width > 6))
{
Settings.display_width = 4;
+ Settings.display_options.type = 0;
}
+ TM1637SetDigitOrder();
}
else if (PinUsed(GPIO_MAX7219DIN) && PinUsed(GPIO_MAX7219CLK) && PinUsed(GPIO_MAX7219CS))
{
@@ -253,7 +256,7 @@ void TM1637Init(void)
TM1637ClearDisplay();
TM1637Dim();
TM1637Data.init_done = true;
- AddLog(LOG_LEVEL_INFO, PSTR("DSP: %s with %d digits"), TM1637Data.model_name, Settings.display_width);
+ AddLog(LOG_LEVEL_INFO, PSTR("DSP: %s with %d digits (type %d)"), TM1637Data.model_name, Settings.display_width, Settings.display_options.type);
}
// Function to display specified ascii char at specified position for MAX7219
@@ -289,6 +292,23 @@ void displayMAX72197Seg(uint8_t pos, uint8_t seg)
max7219display->setRow(MAX7219_ADDR, pos, seg);
}
+// Function to fix order of hardware digits for different TM1637 variants
+void TM1637SetDigitOrder(void) {
+ if (0 == Settings.display_options.type) {
+ for (uint32_t i = 0; i < 6; i++) {
+ TM1637Data.digit_order[i] = i;
+ }
+ }
+ else if (1 == Settings.display_options.type) {
+ TM1637Data.digit_order[0] = 2;
+ TM1637Data.digit_order[1] = 1;
+ TM1637Data.digit_order[2] = 0;
+ TM1637Data.digit_order[3] = 5;
+ TM1637Data.digit_order[4] = 4;
+ TM1637Data.digit_order[5] = 3;
+ }
+}
+
/*********************************************************************************************\
* Displays number without decimal, with/without leading zeros, specifying start-position
* and length, optionally skipping clearing display before displaying the number.
@@ -349,7 +369,7 @@ bool CmndTM1637Number(bool clear)
if (TM1637 == TM1637Data.display_type)
{
rawBytes[0] = tm1637display->encode(pad);
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
else if (TM1638 == TM1637Data.display_type)
tm1638display->displayASCII(i, pad);
@@ -370,7 +390,7 @@ bool CmndTM1637Number(bool clear)
if (TM1637 == TM1637Data.display_type)
{
rawBytes[0] = tm1637display->encode(txt[j]);
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
else if (TM1638 == TM1637Data.display_type)
tm1638display->displayASCII(i, txt[j]);
@@ -456,7 +476,7 @@ bool CmndTM1637Float(bool clear)
}
if ((j + position) > Settings.display_width)
break;
- tm1637display->printRaw(rawBytes, 1, j + position);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[j + position]);
}
}
else if (TM1638 == TM1637Data.display_type)
@@ -626,7 +646,7 @@ void TM1637ScrollText(void)
}
if (TM1637 == TM1637Data.display_type)
{
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
else if (TM1638 == TM1637Data.display_type)
{
@@ -673,7 +693,7 @@ bool CmndTM1637Level(void)
if (TM1637 == TM1637Data.display_type)
{
rawBytes[0] = value;
- tm1637display->printRaw(rawBytes, 1, digit);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[digit]);
}
else if (TM1638 == TM1637Data.display_type)
{
@@ -758,7 +778,7 @@ bool CmndTM1637Raw(void)
if (i > (Settings.display_width - 1))
break;
rawBytes[0] = DATA[i - position];
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
}
else if (TM1638 == TM1637Data.display_type)
@@ -845,7 +865,7 @@ bool CmndTM1637Text(bool clear)
}
if (!dotSkipped && sString[j] == '.')
rawBytes[0] = 128;
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
}
else if (TM1638 == TM1637Data.display_type)
@@ -971,7 +991,7 @@ void TM1637ShowTime()
rawBytes[0] = tm1637display->encode(tm[i]);
if ((millis() % 1000) > 500 && (i == 1))
rawBytes[0] = rawBytes[0] | 128;
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
}
else if (TM1638 == TM1637Data.display_type)
@@ -1084,7 +1104,7 @@ void TM1637Print(char *txt)
uint8_t rawBytes[1];
rawBytes[0] = tm1637display->encode(txt[i]);
// if ((millis() % 1000) > 500 && (i == 1)) { rawBytes[0] = rawBytes[0] | 128; }
- tm1637display->printRaw(rawBytes, 1, i);
+ tm1637display->printRaw(rawBytes, 1, TM1637Data.digit_order[i]);
}
else if (TM1638 == TM1637Data.display_type)
{
From 1ff1a5845501e181d944586ade0fb3b2e1cd1920 Mon Sep 17 00:00:00 2001
From: gemu2015
Date: Mon, 22 Mar 2021 15:39:08 +0100
Subject: [PATCH 33/35] fix compiler error with use_graph
---
tasmota/xdrv_13_display.ino | 81 ++++++++++++++++++++-----------------
1 file changed, 43 insertions(+), 38 deletions(-)
diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino
index e87e34100..ee9777ca6 100755
--- a/tasmota/xdrv_13_display.ino
+++ b/tasmota/xdrv_13_display.ino
@@ -123,6 +123,48 @@ void (* const DisplayCommand[])(void) PROGMEM = {
&CmndDisplayScrollDelay, &CmndDisplayClock, &CmndDisplayTextNC, &CmndDisplayScrollText
};
+#ifdef USE_GRAPH
+
+typedef union {
+ uint8_t data;
+ struct {
+ uint8_t overlay : 1;
+ uint8_t draw : 1;
+ uint8_t nu3 : 1;
+ uint8_t nu4 : 1;
+ uint8_t nu5 : 1;
+ uint8_t nu6 : 1;
+ uint8_t nu7 : 1;
+ uint8_t nu8 : 1;
+ };
+} GFLAGS;
+
+struct GRAPH {
+ uint16_t xp;
+ uint16_t yp;
+ uint16_t xs;
+ uint16_t ys;
+ float ymin;
+ float ymax;
+ float range;
+ uint32_t x_time; // time per x slice in milliseconds
+ uint32_t last_ms;
+ uint32_t last_ms_redrawn;
+ int16_t decimation; // decimation or graph duration in minutes
+ uint16_t dcnt;
+ uint32_t summ;
+ uint16_t xcnt;
+ uint8_t *values;
+ uint8_t xticks;
+ uint8_t yticks;
+ uint8_t last_val;
+ uint8_t color_index;
+ GFLAGS flags;
+};
+
+struct GRAPH *graph[NUM_GRAPHS];
+#endif // USE_GRAPH
+
char *dsp_str;
uint16_t dsp_x;
@@ -2140,46 +2182,9 @@ void DrawAClock(uint16_t rad) {
* Graphics
\*********************************************************************************************/
+
#ifdef USE_GRAPH
-typedef union {
- uint8_t data;
- struct {
- uint8_t overlay : 1;
- uint8_t draw : 1;
- uint8_t nu3 : 1;
- uint8_t nu4 : 1;
- uint8_t nu5 : 1;
- uint8_t nu6 : 1;
- uint8_t nu7 : 1;
- uint8_t nu8 : 1;
- };
-} GFLAGS;
-
-struct GRAPH {
- uint16_t xp;
- uint16_t yp;
- uint16_t xs;
- uint16_t ys;
- float ymin;
- float ymax;
- float range;
- uint32_t x_time; // time per x slice in milliseconds
- uint32_t last_ms;
- uint32_t last_ms_redrawn;
- int16_t decimation; // decimation or graph duration in minutes
- uint16_t dcnt;
- uint32_t summ;
- uint16_t xcnt;
- uint8_t *values;
- uint8_t xticks;
- uint8_t yticks;
- uint8_t last_val;
- uint8_t color_index;
- GFLAGS flags;
-};
-
-struct GRAPH *graph[NUM_GRAPHS];
#define TICKLEN 4
void ClrGraph(uint16_t num) {
From 3757e1d7889789d36f6453fbc16603dd93b8f59a Mon Sep 17 00:00:00 2001
From: gemu2015
Date: Mon, 22 Mar 2021 15:39:55 +0100
Subject: [PATCH 34/35] fix image weblink
---
tasmota/xdrv_10_scripter.ino | 40 +++++++++++++++++++++++-------------
1 file changed, 26 insertions(+), 14 deletions(-)
diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino
index 97ebfad30..326c0df47 100755
--- a/tasmota/xdrv_10_scripter.ino
+++ b/tasmota/xdrv_10_scripter.ino
@@ -67,8 +67,8 @@ keywords if then else endif, or, and are better readable for beginners (others m
#define MAX_SARRAY_NUM 32
-//uint32_t EncodeLightId(uint8_t relay_id);
-//uint32_t DecodeLightId(uint32_t hue_id);
+uint32_t EncodeLightId(uint8_t relay_id);
+uint32_t DecodeLightId(uint32_t hue_id);
#define SPECIAL_EEPMODE_SIZE 6200
@@ -776,7 +776,7 @@ char *script;
script_mem_size += 16;
uint8_t *script_mem;
- script_mem = (uint8_t*)calloc(script_mem_size, 1);
+ script_mem = (uint8_t*)special_malloc(script_mem_size);
if (!script_mem) {
if (imemptr) free(imemptr);
return -4;
@@ -2175,7 +2175,7 @@ chknext:
if (ef) {
uint16_t fsiz = ef.size();
if (fsiz<2048) {
- char *script = (char*)calloc(fsiz + 16, 1);
+ char *script = (char*)special_malloc(fsiz + 16);
if (script) {
ef.read((uint8_t*)script,fsiz);
execute_script(script);
@@ -3643,15 +3643,22 @@ int32_t UpdVar(char *vname, float *fvar, uint32_t mode) {
if (vtype == NUM_RES || (vtype & STYPE) == 0) {
if (mode) {
// set var
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("write from homekit: %s - %d"), vname, (uint32_t)res);
index = glob_script_mem.type[ind.index].index;
glob_script_mem.fvars[index] = res;
glob_script_mem.type[ind.index].bits.changed = 1;
+#ifdef USE_SCRIPT_GLOBVARS
+ if (glob_script_mem.type[ind.index].bits.global) {
+ script_udp_sendvar(vname, &res, 0);
+ }
+#endif //USE_SCRIPT_GLOBVARS
return 0;
} else {
// get var
//index = glob_script_mem.type[ind.index].index;
int32_t ret = glob_script_mem.type[ind.index].bits.hchanged;
glob_script_mem.type[ind.index].bits.hchanged = 0;
+ //AddLog(LOG_LEVEL_DEBUG, PSTR("read from homekit: %s - %d - %d"), vname, (uint32_t)*fvar, ret);
return ret;
}
} else {
@@ -6357,7 +6364,8 @@ void ScriptGetSDCard(void) {
if (!HttpCheckPriviledgedAccess()) { return; }
String stmp = Webserver->uri();
- char *cp = strstr_P(stmp.c_str(), PSTR("/sdc/"));
+
+ char *cp = strstr_P(stmp.c_str(), PSTR("/ufs/"));
// if (cp) Serial.printf(">>>%s\n",cp);
if (cp) {
#ifdef ESP32
@@ -6365,13 +6373,15 @@ void ScriptGetSDCard(void) {
#else
cp += 5;
#endif
- if (strstr_P(cp, PSTR("scrdmp.bmp"))) {
- SendFile(cp);
- return;
- } else {
- if (ufsp->exists(cp)) {
+ if (ufsp) {
+ if (strstr_P(cp, PSTR("scrdmp.bmp"))) {
SendFile(cp);
return;
+ } else {
+ if (ufsp->exists(cp)) {
+ SendFile(cp);
+ return;
+ }
}
}
}
@@ -6392,7 +6402,6 @@ char buff[512];
#ifdef USE_DISPLAY_DUMP
char *sbmp = strstr_P(fname, PSTR("scrdmp.bmp"));
if (sbmp) {
- mime = "image/bmp";
sflg = 1;
}
#endif // USE_DISPLAY_DUMP
@@ -6419,7 +6428,7 @@ char buff[512];
#define infoHeaderSize 40
if (buffer) {
uint8_t *bp = buffer;
- uint8_t *lbuf = (uint8_t*)calloc(Settings.display_width + 2, 3);
+ uint8_t *lbuf = (uint8_t*)special_malloc(Settings.display_width + 2);
uint8_t *lbp;
uint8_t fileHeader[fileHeaderSize];
createBitmapFileHeader(Settings.display_height , Settings.display_width , fileHeader);
@@ -7679,7 +7688,7 @@ bool Xdrv10(uint8_t function)
// we have a file system
AddLog(LOG_LEVEL_INFO,PSTR("UFILESYSTEM OK!"));
char *script;
- script = (char*)calloc(UFSYS_SIZE + 4, 1);
+ script = (char*)special_malloc(UFSYS_SIZE + 4);
if (!script) break;
glob_script_mem.script_ram = script;
glob_script_mem.script_size = UFSYS_SIZE;
@@ -7850,6 +7859,10 @@ bool Xdrv10(uint8_t function)
Webserver->on("/sfd", ScriptFullWebpage);
}
#endif // SCRIPT_FULL_WEBPAGE
+
+#ifdef USE_UFILESYS
+ Webserver->onNotFound(ScriptGetSDCard);
+#endif // USE_UFILESYS
}
break;
#endif // USE_SCRIPT_WEB_DISPLAY
@@ -7858,7 +7871,6 @@ bool Xdrv10(uint8_t function)
Webserver->on("/ta",HTTP_POST, HandleScriptTextareaConfiguration);
Webserver->on("/exs", HTTP_POST,[]() { Webserver->sendHeader("Location","/exs");Webserver->send(303);}, script_upload_start);
Webserver->on("/exs", HTTP_GET, ScriptExecuteUploadSuccess);
- break;
#endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART:
if (bitRead(Settings.rule_enabled, 0)) {
From b0cd14e4f9a876cee5df758a8d373d813f40de9e Mon Sep 17 00:00:00 2001
From: gemu2015
Date: Mon, 22 Mar 2021 15:40:16 +0100
Subject: [PATCH 35/35] some fixes
---
tasmota/homekit.c | 57 ++++++++++++++++++++++++++++++-----------------
1 file changed, 36 insertions(+), 21 deletions(-)
diff --git a/tasmota/homekit.c b/tasmota/homekit.c
index 5bd539ddb..92e19543c 100755
--- a/tasmota/homekit.c
+++ b/tasmota/homekit.c
@@ -56,6 +56,7 @@ uint8_t hk_services;
extern void Ext_Replace_Cmd_Vars(char *srcbuf, uint32_t srcsize, char *dstbuf, uint32_t dstsize);
extern uint32_t Ext_UpdVar(char *vname, float *fvar, uint32_t mode);
+extern void Ext_toLog(char *str);
#define MAX_HAP_DEFS 16
struct HAP_DESC {
@@ -64,6 +65,7 @@ struct HAP_DESC {
char var2_name[12];
char var3_name[12];
char var4_name[12];
+ char var5_name[12];
uint8_t hap_cid;
uint8_t type;
hap_acc_t *accessory;
@@ -157,8 +159,8 @@ const struct HAP_CHAR_TABLE {
{HAP_CHAR_UUID_CURRENT_RELATIVE_HUMIDITY,'f',0},
{HAP_CHAR_UUID_CURRENT_AMBIENT_LIGHT_LEVEL,'f',0},
{HAP_CHAR_UUID_BATTERY_LEVEL,'u',0},
- {HAP_CHAR_UUID_STATUS_LOW_BATTERY,'b',1},
- {HAP_CHAR_UUID_CHARGING_STATE,'b',2},
+ {HAP_CHAR_UUID_STATUS_LOW_BATTERY,'u',1},
+ {HAP_CHAR_UUID_CHARGING_STATE,'u',2},
{HAP_CHAR_UUID_ON,'b',0},
{HAP_CHAR_UUID_HUE,'f',1},
{HAP_CHAR_UUID_SATURATION,'f',2},
@@ -282,7 +284,7 @@ void hap_update_from_vars(void) {
new_val.u = fvar;
hap_char_update_val(hc, &new_val);
}
- hc = hap_serv_get_char_by_uuid(hap_devs[cnt].service, HAP_CHAR_UUID_STATUS_LOW_BATTERY);
+ hc = hap_serv_get_char_by_uuid(hap_devs[cnt].service, HAP_CHAR_UUID_CHARGING_STATE);
if (Ext_UpdVar(hap_devs[cnt].var3_name, &fvar, 0)) {
new_val.u = fvar;
hap_char_update_val(hc, &new_val);
@@ -332,6 +334,13 @@ void hap_update_from_vars(void) {
new_val.u = fvar;
hap_char_update_val(hc, &new_val);
}
+ if (hap_devs[cnt].var5_name[0]) {
+ hc = hap_serv_get_char_by_uuid(hap_devs[cnt].service, HAP_CHAR_UUID_COLOR_TEMPERATURE);
+ if (Ext_UpdVar(hap_devs[cnt].var5_name, &fvar, 0)) {
+ new_val.u = fvar;
+ hap_char_update_val(hc, &new_val);
+ }
+ }
break;
}
}
@@ -454,6 +463,7 @@ uint32_t str2c(char **sp, char *vp, uint32_t len) {
} else {
if (strlen(*sp)) {
strlcpy(vp, *sp, len);
+ *sp = lp + strlen(*sp);
return 0;
}
}
@@ -520,19 +530,16 @@ static void smart_outlet_thread_entry(void *p) {
if (str2c(&lp1, hap_devs[index].var_name, sizeof(hap_devs[index].var_name))) {
goto nextline;
}
- if (hap_devs[index].hap_cid == HAP_CID_LIGHTING) {
- // get 3 add vars
- if (str2c(&lp1, hap_devs[index].var2_name, sizeof(hap_devs[index].var2_name))) {
- goto nextline;
- }
- if (str2c(&lp1, hap_devs[index].var3_name, sizeof(hap_devs[index].var3_name))) {
- goto nextline;
- }
- if (str2c(&lp1, hap_devs[index].var4_name, sizeof(hap_devs[index].var4_name))) {
- goto nextline;
- }
- }
+ hap_devs[index].var2_name[0] = 0;
+ hap_devs[index].var3_name[0] = 0;
+ hap_devs[index].var4_name[0] = 0;
+ hap_devs[index].var5_name[0] = 0;
+
+ str2c(&lp1, hap_devs[index].var2_name, sizeof(hap_devs[index].var2_name));
+ str2c(&lp1, hap_devs[index].var3_name, sizeof(hap_devs[index].var3_name));
+ str2c(&lp1, hap_devs[index].var4_name, sizeof(hap_devs[index].var4_name));
+ str2c(&lp1, hap_devs[index].var5_name, sizeof(hap_devs[index].var5_name));
hap_acc_cfg_t hap_cfg;
hap_cfg.name = hap_devs[index].hap_name;
@@ -557,12 +564,20 @@ static void smart_outlet_thread_entry(void *p) {
{ float fvar = 0;
Ext_UpdVar(hap_devs[index].var_name, &fvar, 0);
hap_devs[index].service = hap_serv_lightbulb_create(fvar);
- Ext_UpdVar(hap_devs[index].var2_name, &fvar, 0);
- ret |= hap_serv_add_char(hap_devs[index].service, hap_char_hue_create(fvar));
- Ext_UpdVar(hap_devs[index].var3_name, &fvar, 0);
- ret |= hap_serv_add_char(hap_devs[index].service, hap_char_saturation_create(fvar));
+ if (hap_devs[index].var2_name[0]) {
+ Ext_UpdVar(hap_devs[index].var2_name, &fvar, 0);
+ ret |= hap_serv_add_char(hap_devs[index].service, hap_char_hue_create(fvar));
+ }
+ if (hap_devs[index].var3_name[0]) {
+ Ext_UpdVar(hap_devs[index].var3_name, &fvar, 0);
+ ret |= hap_serv_add_char(hap_devs[index].service, hap_char_saturation_create(fvar));
+ }
Ext_UpdVar(hap_devs[index].var4_name, &fvar, 0);
ret |= hap_serv_add_char(hap_devs[index].service, hap_char_brightness_create(fvar));
+ if (hap_devs[index].var5_name[0]) {
+ Ext_UpdVar(hap_devs[index].var5_name, &fvar, 0);
+ ret |= hap_serv_add_char(hap_devs[index].service, hap_char_color_temperature_create(fvar));
+ }
}
break;
case HAP_CID_OUTLET:
@@ -681,14 +696,14 @@ nextline:
// vTaskDelete(NULL);
while (1) {
delay(500);
- // hap_update_from_vars();
+ hap_update_from_vars();
}
}
}
#define HK_PASSCODE "111-11-111"
int hap_loop_stop(void);
-extern void Ext_toLog(char *str);
+
void homekit_main(char *desc, uint32_t flag ) {
if (desc) {