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