2017-01-28 13:41:01 +00:00
/*
2019-10-27 10:13:24 +00:00
support . ino - support for Tasmota
2017-05-13 12:02:10 +01:00
2021-01-01 12:44:04 +00:00
Copyright ( C ) 2021 Theo Arends
2017-05-13 12:02:10 +01:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
2017-01-28 13:41:01 +00:00
*/
2019-11-18 10:02:04 +00:00
extern " C " {
extern struct rst_info resetInfo ;
}
2017-02-28 15:01:48 +00:00
/*********************************************************************************************\
* Watchdog extension ( https : //github.com/esp8266/Arduino/issues/1532)
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-12-04 16:39:04 +00:00
# ifdef ESP8266
2018-11-20 14:00:24 +00:00
# include <Ticker.h>
2017-02-28 15:01:48 +00:00
Ticker tickerOSWatch ;
2019-03-31 10:59:04 +01:00
const uint32_t OSWATCH_RESET_TIME = 120 ;
2017-02-28 15:01:48 +00:00
2017-10-18 17:22:34 +01:00
static unsigned long oswatch_last_loop_time ;
2019-01-28 13:08:33 +00:00
uint8_t oswatch_blocked_loop = 0 ;
2017-02-28 15:01:48 +00:00
2017-04-03 15:38:15 +01:00
# ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception
2021-04-02 16:14:08 +01:00
//void OsWatchTicker() IRAM_ATTR;
2017-04-03 15:38:15 +01:00
# endif // USE_WS2812_DMA
2021-12-04 16:39:04 +00:00
void OsWatchTicker ( void ) {
2019-07-28 12:54:52 +01:00
uint32_t t = millis ( ) ;
2020-10-22 14:23:22 +01:00
uint32_t last_run = t - oswatch_last_loop_time ;
2017-02-28 15:01:48 +00:00
# ifdef DEBUG_THEO
2020-02-20 09:07:00 +00:00
int32_t rssi = WiFi . RSSI ( ) ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d " ) , ESP_getFreeHeap ( ) , WifiGetRssiAsQuality ( rssi ) , rssi , last_run ) ;
2017-02-28 15:01:48 +00:00
# endif // DEBUG_THEO
2017-04-25 17:24:42 +01:00
if ( last_run > = ( OSWATCH_RESET_TIME * 1000 ) ) {
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_OSWATCH " " D_BLOCKED_LOOP ". " D_RESTARTING)); // Save iram space
2017-10-18 17:22:34 +01:00
RtcSettings . oswatch_blocked_loop = 1 ;
RtcSettingsSave ( ) ;
2019-12-28 13:54:26 +00:00
2017-09-02 13:37:02 +01:00
// ESP.restart(); // normal reboot
2019-12-28 13:54:26 +00:00
// ESP.reset(); // hard reset
// Force an exception to get a stackdump
2021-01-11 13:50:43 +00:00
// ESP32: Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled.
2019-12-28 13:54:26 +00:00
volatile uint32_t dummy ;
dummy = * ( ( uint32_t * ) 0x00000000 ) ;
2020-11-12 18:38:21 +00:00
( void ) dummy ; // avoid compiler warning
2017-02-28 15:01:48 +00:00
}
}
2021-12-04 16:39:04 +00:00
void OsWatchInit ( void ) {
2017-10-18 17:22:34 +01:00
oswatch_blocked_loop = RtcSettings . oswatch_blocked_loop ;
RtcSettings . oswatch_blocked_loop = 0 ;
oswatch_last_loop_time = millis ( ) ;
tickerOSWatch . attach_ms ( ( ( OSWATCH_RESET_TIME / 3 ) * 1000 ) , OsWatchTicker ) ;
2017-02-28 15:01:48 +00:00
}
2021-12-04 16:39:04 +00:00
void OsWatchLoop ( void ) {
2017-10-18 17:22:34 +01:00
oswatch_last_loop_time = millis ( ) ;
2017-02-28 15:01:48 +00:00
// while(1) delay(1000); // this will trigger the os watch
}
2021-12-04 16:39:04 +00:00
bool OsWatchBlockedLoop ( void ) {
2019-11-20 20:49:57 +00:00
return oswatch_blocked_loop ;
}
2021-12-04 16:39:04 +00:00
# else // Anything except ESP8266
void OsWatchInit ( void ) { }
void OsWatchLoop ( void ) { }
bool OsWatchBlockedLoop ( void ) {
return false ;
}
# endif // ESP8266
uint32_t ResetReason ( void ) {
2019-11-20 20:49:57 +00:00
/*
user_interface . h
REASON_DEFAULT_RST = 0 , // "Power on" normal startup by power on
REASON_WDT_RST = 1 , // "Hardware Watchdog" hardware watch dog reset
REASON_EXCEPTION_RST = 2 , // "Exception" exception reset, GPIO status won’ t change
REASON_SOFT_WDT_RST = 3 , // "Software Watchdog" software watch dog reset, GPIO status won’ t change
REASON_SOFT_RESTART = 4 , // "Software/System restart" software restart ,system_restart , GPIO status won’ t change
REASON_DEEP_SLEEP_AWAKE = 5 , // "Deep-Sleep Wake" wake up from deep-sleep
REASON_EXT_SYS_RST = 6 // "External System" external system reset
*/
2020-04-16 17:33:26 +01:00
return ESP_ResetInfoReason ( ) ;
2019-11-20 20:49:57 +00:00
}
2022-11-03 16:26:54 +00:00
bool ResetReasonPowerOn ( void ) {
uint32_t reset_reason = ESP_ResetInfoReason ( ) ;
return ( ( reset_reason = = REASON_DEFAULT_RST ) | | ( reset_reason = = REASON_EXT_SYS_RST ) ) ;
}
2021-12-04 16:39:04 +00:00
String GetResetReason ( void ) {
if ( OsWatchBlockedLoop ( ) ) {
2019-10-30 13:08:43 +00:00
char buff [ 32 ] ;
2018-01-06 16:34:42 +00:00
strncpy_P ( buff , PSTR ( D_JSON_BLOCKED_LOOP ) , sizeof ( buff ) ) ;
2017-02-28 15:01:48 +00:00
return String ( buff ) ;
} else {
2020-04-10 17:24:08 +01:00
return ESP_getResetReason ( ) ;
2017-02-28 15:01:48 +00:00
}
}
2021-01-01 16:04:36 +00:00
# ifdef ESP32
2020-12-31 11:36:35 +00:00
/*********************************************************************************************\
* ESP32 AutoMutex
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//////////////////////////////////////////
// automutex.
// create a mute in your driver with:
// void *mutex = nullptr;
//
// then protect any function with
2021-01-02 09:02:11 +00:00
// TasAutoMutex m(&mutex, "somename");
// - mutex is automatically initialised if not already intialised.
2020-12-31 11:36:35 +00:00
// - it will be automagically released when the function is over.
// - the same thread can take multiple times (recursive).
// - advanced options m.give() and m.take() allow you fine control within a function.
2021-01-02 09:02:11 +00:00
// - if take=false at creat, it will not be initially taken.
// - name is used in serial log of mutex deadlock.
// - maxWait in ticks is how long it will wait before failing in a deadlock scenario (and then emitting on serial)
2020-12-31 11:36:35 +00:00
class TasAutoMutex {
SemaphoreHandle_t mutex ;
bool taken ;
2021-01-02 09:02:11 +00:00
int maxWait ;
const char * name ;
2020-12-31 11:36:35 +00:00
public :
2021-09-06 18:52:20 +01:00
TasAutoMutex ( SemaphoreHandle_t * mutex , const char * name = " " , int maxWait = 40 , bool take = true ) ;
2020-12-31 11:36:35 +00:00
~ TasAutoMutex ( ) ;
void give ( ) ;
void take ( ) ;
2021-09-06 18:52:20 +01:00
static void init ( SemaphoreHandle_t * ptr ) ;
2020-12-31 11:36:35 +00:00
} ;
//////////////////////////////////////////
2021-09-06 18:52:20 +01:00
TasAutoMutex : : TasAutoMutex ( SemaphoreHandle_t * mutex , const char * name , int maxWait , bool take ) {
2021-01-01 16:04:36 +00:00
if ( mutex ) {
2021-01-02 09:02:11 +00:00
if ( ! ( * mutex ) ) {
2021-01-02 13:59:02 +00:00
TasAutoMutex : : init ( mutex ) ;
2021-01-02 09:02:11 +00:00
}
2021-09-06 18:52:20 +01:00
this - > mutex = * mutex ;
2021-01-02 09:02:11 +00:00
this - > maxWait = maxWait ;
this - > name = name ;
2021-01-01 16:04:36 +00:00
if ( take ) {
2021-01-02 09:02:11 +00:00
this - > taken = xSemaphoreTakeRecursive ( this - > mutex , this - > maxWait ) ;
2021-10-02 13:49:50 +01:00
// if (!this->taken){
// Serial.printf("\r\nMutexfail %s\r\n", this->name);
// }
2020-12-31 11:36:35 +00:00
}
} else {
2021-01-01 16:04:36 +00:00
this - > mutex = ( SemaphoreHandle_t ) nullptr ;
2020-12-31 11:36:35 +00:00
}
}
2021-01-01 16:04:36 +00:00
TasAutoMutex : : ~ TasAutoMutex ( ) {
if ( this - > mutex ) {
if ( this - > taken ) {
2020-12-31 11:36:35 +00:00
xSemaphoreGiveRecursive ( this - > mutex ) ;
this - > taken = false ;
}
}
}
2021-09-06 18:52:20 +01:00
void TasAutoMutex : : init ( SemaphoreHandle_t * ptr ) {
2020-12-31 11:36:35 +00:00
SemaphoreHandle_t mutex = xSemaphoreCreateRecursiveMutex ( ) ;
2021-09-06 18:52:20 +01:00
( * ptr ) = mutex ;
2021-01-01 16:04:36 +00:00
// needed, else for ESP8266 as we will initialis more than once in logging
// (*ptr) = (void *) 1;
2020-12-31 11:36:35 +00:00
}
2021-01-01 16:04:36 +00:00
void TasAutoMutex : : give ( ) {
if ( this - > mutex ) {
if ( this - > taken ) {
2020-12-31 11:36:35 +00:00
xSemaphoreGiveRecursive ( this - > mutex ) ;
this - > taken = false ;
}
}
}
2021-01-01 16:04:36 +00:00
void TasAutoMutex : : take ( ) {
if ( this - > mutex ) {
if ( ! this - > taken ) {
2021-01-02 09:02:11 +00:00
this - > taken = xSemaphoreTakeRecursive ( this - > mutex , this - > maxWait ) ;
2021-10-02 13:49:50 +01:00
// if (!this->taken){
// Serial.printf("\r\nMutexfail %s\r\n", this->name);
// }
2020-12-31 11:36:35 +00:00
}
}
}
2021-01-01 16:04:36 +00:00
# endif // ESP32
2020-12-31 11:36:35 +00:00
2021-01-02 09:02:11 +00:00
2017-03-25 16:24:11 +00:00
/*********************************************************************************************\
2018-01-30 13:14:55 +00:00
* Miscellaneous
2017-03-25 16:24:11 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-09-23 16:13:10 +01:00
/*
2020-08-20 16:58:58 +01:00
String GetBinary ( const void * ptr , size_t count ) {
uint32_t value = * ( uint32_t * ) ptr ;
value < < = ( 32 - count ) ;
String result ;
result . reserve ( count + 1 ) ;
for ( uint32_t i = 0 ; i < count ; i + + ) {
result + = ( value & 0x80000000 ) ? ' 1 ' : ' 0 ' ;
value < < = 1 ;
}
return result ;
}
2020-09-23 16:13:10 +01:00
*/
String GetBinary8 ( uint8_t value , size_t count ) {
if ( count > 8 ) { count = 8 ; }
value < < = ( 8 - count ) ;
String result ;
result . reserve ( count + 1 ) ;
for ( uint32_t i = 0 ; i < count ; i + + ) {
result + = ( value & 0x80 ) ? ' 1 ' : ' 0 ' ;
value < < = 1 ;
}
return result ;
}
2020-08-20 15:22:40 +01:00
2018-02-01 15:18:00 +00:00
// Get span until single character in string
size_t strchrspn ( const char * str1 , int character )
2018-01-30 13:14:55 +00:00
{
2018-02-01 15:18:00 +00:00
size_t ret = 0 ;
char * start = ( char * ) str1 ;
char * end = strchr ( str1 , character ) ;
if ( end ) ret = end - start ;
return ret ;
2018-01-30 13:14:55 +00:00
}
2020-08-04 15:33:05 +01:00
uint32_t ChrCount ( const char * str , const char * delim ) {
uint32_t count = 0 ;
char * read = ( char * ) str ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
if ( ch = = * delim ) { count + + ; }
}
return count ;
}
2021-01-30 11:27:48 +00:00
uint32_t ArgC ( void ) {
2021-01-30 16:00:50 +00:00
return ( XdrvMailbox . data_len > 0 ) ? ChrCount ( XdrvMailbox . data , " , " ) + 1 : 0 ;
2021-01-30 11:27:48 +00:00
}
2018-07-23 05:32:54 +01:00
// Function to return a substring defined by a delimiter at an index
2021-01-29 15:16:20 +00:00
char * subStr ( char * dest , char * str , const char * delim , int index ) {
2021-01-30 11:27:48 +00:00
char * write = dest ;
char * read = str ;
char ch = ' . ' ;
while ( index & & ( ch ! = ' \0 ' ) ) {
ch = * read + + ;
if ( strchr ( delim , ch ) ) {
index - - ;
if ( index ) { write = dest ; }
} else {
* write + + = ch ;
}
2021-01-29 15:16:20 +00:00
}
2021-01-30 11:27:48 +00:00
* write = ' \0 ' ;
dest = Trim ( dest ) ;
return dest ;
}
char * ArgV ( char * dest , int index ) {
return subStr ( dest , XdrvMailbox . data , " , " , index ) ;
2018-07-23 05:32:54 +01:00
}
2021-01-30 16:00:50 +00:00
uint32_t ArgVul ( uint32_t * args , uint32_t count ) {
uint32_t argc = ArgC ( ) ;
if ( argc > count ) { argc = count ; }
count = argc ;
if ( argc ) {
char argument [ XdrvMailbox . data_len ] ;
for ( uint32_t i = 0 ; i < argc ; i + + ) {
if ( strlen ( ArgV ( argument , i + 1 ) ) ) {
args [ i ] = strtoul ( argument , nullptr , 0 ) ;
} else {
count - - ;
}
}
}
return count ;
}
uint32_t ParseParameters ( uint32_t count , uint32_t * params ) {
// Destroys XdrvMailbox.data
2021-01-30 13:52:53 +00:00
char * p ;
uint32_t i = 0 ;
for ( char * str = strtok_r ( XdrvMailbox . data , " , " , & p ) ; str & & i < count ; str = strtok_r ( nullptr , " , " , & p ) , i + + ) {
params [ i ] = strtoul ( str , nullptr , 0 ) ;
}
return i ;
}
2019-07-01 17:20:43 +01:00
float CharToFloat ( const char * str )
2018-04-05 11:49:43 +01:00
{
// simple ascii to double, because atof or strtod are too large
char strbuf [ 24 ] ;
2018-09-02 10:52:24 +01:00
strlcpy ( strbuf , str , sizeof ( strbuf ) ) ;
2019-03-07 17:18:30 +00:00
char * pt = strbuf ;
2022-04-19 14:44:53 +01:00
if ( * pt = = ' \0 ' ) { return 0.0f ; }
2020-08-04 15:33:05 +01:00
2021-08-16 10:11:46 +01:00
while ( ( * pt ! = ' \0 ' ) & & isspace ( * pt ) ) { pt + + ; } // Trim leading spaces
2019-03-07 17:18:30 +00:00
signed char sign = 1 ;
if ( * pt = = ' - ' ) { sign = - 1 ; }
2020-06-15 17:27:04 +01:00
if ( * pt = = ' - ' | | * pt = = ' + ' ) { pt + + ; } // Skip any sign
2019-03-07 17:18:30 +00:00
2019-07-01 17:20:43 +01:00
float left = 0 ;
2019-03-07 17:18:30 +00:00
if ( * pt ! = ' . ' ) {
left = atoi ( pt ) ; // Get left part
while ( isdigit ( * pt ) ) { pt + + ; } // Skip number
}
2019-07-01 17:20:43 +01:00
float right = 0 ;
2019-03-07 17:18:30 +00:00
if ( * pt = = ' . ' ) {
pt + + ;
2020-06-15 17:27:04 +01:00
uint32_t max_decimals = 0 ;
while ( ( max_decimals < 8 ) & & isdigit ( pt [ max_decimals ] ) ) { max_decimals + + ; }
pt [ max_decimals ] = ' \0 ' ; // Limit decimals to float max of 8
2019-03-07 17:18:30 +00:00
right = atoi ( pt ) ; // Decimal part
while ( isdigit ( * pt ) ) {
pt + + ;
2019-07-01 17:20:43 +01:00
right / = 10.0f ;
2018-04-05 11:49:43 +01:00
}
}
2019-03-07 17:18:30 +00:00
2019-07-01 17:20:43 +01:00
float result = left + right ;
2019-03-07 17:18:30 +00:00
if ( sign < 0 ) {
return - result ; // Add negative sign
}
2018-04-17 14:34:18 +01:00
return result ;
2018-04-05 11:49:43 +01:00
}
2018-08-23 15:05:51 +01:00
int TextToInt ( char * str )
{
char * p ;
uint8_t radix = 10 ;
if ( ' # ' = = str [ 0 ] ) {
radix = 16 ;
str + + ;
}
return strtol ( str , & p , radix ) ;
}
2018-01-20 11:12:39 +00:00
char * dtostrfd ( double number , unsigned char prec , char * s )
2017-09-02 13:37:02 +01:00
{
2018-10-25 13:03:34 +01:00
if ( ( isnan ( number ) ) | | ( isinf ( number ) ) ) { // Fix for JSON output (https://stackoverflow.com/questions/1423081/json-left-out-infinity-and-nan-json-status-in-ecmascript)
2021-01-18 20:48:04 +00:00
strcpy_P ( s , PSTR ( " null " ) ) ;
2018-10-25 09:44:59 +01:00
return s ;
} else {
return dtostrf ( number , 1 , prec , s ) ;
}
2017-09-02 13:37:02 +01:00
}
2019-07-27 10:13:41 +01:00
char * Unescape ( char * buffer , uint32_t * size )
2018-03-20 13:31:11 +00:00
{
uint8_t * read = ( uint8_t * ) buffer ;
uint8_t * write = ( uint8_t * ) buffer ;
2019-07-27 10:13:41 +01:00
int32_t start_size = * size ;
int32_t end_size = * size ;
2018-03-20 13:31:11 +00:00
uint8_t che = 0 ;
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: UnescapeIn %*_H"), *size, (uint8_t*)buffer);
2019-01-17 16:48:34 +00:00
2018-03-20 13:31:11 +00:00
while ( start_size > 0 ) {
uint8_t ch = * read + + ;
start_size - - ;
if ( ch ! = ' \\ ' ) {
* write + + = ch ;
} else {
if ( start_size > 0 ) {
uint8_t chi = * read + + ;
start_size - - ;
end_size - - ;
switch ( chi ) {
case ' \\ ' : che = ' \\ ' ; break ; // 5C Backslash
case ' a ' : che = ' \a ' ; break ; // 07 Bell (Alert)
case ' b ' : che = ' \b ' ; break ; // 08 Backspace
case ' e ' : che = ' \e ' ; break ; // 1B Escape
case ' f ' : che = ' \f ' ; break ; // 0C Formfeed
case ' n ' : che = ' \n ' ; break ; // 0A Linefeed (Newline)
case ' r ' : che = ' \r ' ; break ; // 0D Carriage return
2018-03-20 15:28:18 +00:00
case ' s ' : che = ' ' ; break ; // 20 Space
2018-03-20 13:31:11 +00:00
case ' t ' : che = ' \t ' ; break ; // 09 Horizontal tab
case ' v ' : che = ' \v ' ; break ; // 0B Vertical tab
2019-01-17 16:48:34 +00:00
case ' x ' : {
uint8_t * start = read ;
che = ( uint8_t ) strtol ( ( const char * ) read , ( char * * ) & read , 16 ) ;
start_size - = ( uint16_t ) ( read - start ) ;
end_size - = ( uint16_t ) ( read - start ) ;
break ;
}
case ' " ' : che = ' \" ' ; break ; // 22 Quotation mark
2018-03-20 13:31:11 +00:00
// case '?': che = '\?'; break; // 3F Question mark
default : {
che = chi ;
* write + + = ch ;
end_size + + ;
}
}
* write + + = che ;
}
}
}
* size = end_size ;
2019-05-09 10:26:20 +01:00
* write + + = 0 ; // add the end string pointer reference
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: UnescapeOut %*_H"), *size, (uint8_t*)buffer);
2019-01-17 16:48:34 +00:00
2018-03-20 13:31:11 +00:00
return buffer ;
}
2020-10-10 16:00:37 +01:00
char * RemoveSpace ( char * p ) {
// Remove white-space character (' ','\t','\n','\v','\f','\r')
2018-10-14 11:53:11 +01:00
char * write = p ;
char * read = p ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
if ( ! isspace ( ch ) ) {
* write + + = ch ;
}
}
return p ;
}
2022-09-22 12:24:07 +01:00
// remove spaces at the beginning and end of the string (but not in the middle)
char * TrimSpace ( char * p ) {
// Remove white-space character (' ','\t','\n','\v','\f','\r')
char * write = p ;
char * read = p ;
char ch = ' . ' ;
// skip all leading spaces
while ( isspace ( * read ) ) {
read + + ;
}
// copy the rest
do {
ch = * read + + ;
* write + + = ch ;
} while ( ch ! = ' \0 ' ) ;
// move to end
read = p + strlen ( p ) ;
// move backwards
while ( p ! = read ) {
read - - ;
if ( isspace ( * read ) ) {
* read = ' \0 ' ;
} else {
break ;
}
}
return p ;
}
2020-10-10 16:00:37 +01:00
char * RemoveControlCharacter ( char * p ) {
// Remove control character (0x00 .. 0x1F and 0x7F)
2020-10-10 15:44:35 +01:00
char * write = p ;
char * read = p ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
if ( ! iscntrl ( ch ) ) {
* write + + = ch ;
}
}
2020-10-19 11:39:27 +01:00
* write + + = ' \0 ' ;
2020-10-10 15:44:35 +01:00
return p ;
}
2020-12-26 11:18:16 +00:00
char * ReplaceChar ( char * p , char find , char replace ) {
2019-12-16 14:13:57 +00:00
char * write = ( char * ) p ;
char * read = ( char * ) p ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
2020-12-26 11:18:16 +00:00
if ( ch = = find ) {
ch = replace ;
2019-12-16 14:13:57 +00:00
}
* write + + = ch ;
}
return p ;
}
2020-12-26 11:18:16 +00:00
char * ReplaceCommaWithDot ( char * p ) {
return ReplaceChar ( p , ' , ' , ' . ' ) ;
}
2019-03-26 16:10:07 +00:00
char * LowerCase ( char * dest , const char * source )
{
char * write = dest ;
const char * read = source ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
* write + + = tolower ( ch ) ;
}
return dest ;
}
2018-03-23 16:20:20 +00:00
char * UpperCase ( char * dest , const char * source )
{
char * write = dest ;
const char * read = source ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
* write + + = toupper ( ch ) ;
}
return dest ;
}
char * UpperCase_P ( char * dest , const char * source )
{
char * write = dest ;
const char * read = source ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = pgm_read_byte ( read + + ) ;
* write + + = toupper ( ch ) ;
}
return dest ;
}
2022-10-12 10:02:54 +01:00
char * SetStr ( const char * str ) {
if ( nullptr = = str ) { str = PSTR ( " " ) ; } // nullptr is considered empty string
size_t str_len = strlen ( str ) ;
if ( 0 = = str_len ) { return EmptyStr ; } // return empty string
char * new_str = ( char * ) malloc ( str_len + 1 ) ;
if ( nullptr = = new_str ) { return EmptyStr ; } // return empty string
strlcpy ( new_str , str , str_len + 1 ) ;
return new_str ;
}
2021-05-19 15:39:56 +01:00
bool StrCaseStr_P ( const char * source , const char * search ) {
2021-05-02 14:28:47 +01:00
char case_source [ strlen_P ( source ) + 1 ] ;
2021-04-25 16:22:38 +01:00
UpperCase_P ( case_source , source ) ;
2021-05-02 14:28:47 +01:00
char case_search [ strlen_P ( search ) + 1 ] ;
2021-04-25 16:22:38 +01:00
UpperCase_P ( case_search , search ) ;
2021-05-19 15:39:56 +01:00
return ( strstr ( case_source , case_search ) ! = nullptr ) ;
2021-04-25 16:22:38 +01:00
}
2021-08-14 15:28:20 +01:00
bool IsNumeric ( const char * value ) {
2021-08-16 10:44:27 +01:00
// Test for characters '-.0123456789'
2021-08-14 15:28:20 +01:00
char * digit = ( char * ) value ;
while ( isdigit ( * digit ) | | * digit = = ' . ' | | * digit = = ' - ' ) { digit + + ; }
return ( * digit = = ' \0 ' ) ;
}
2021-08-16 10:11:46 +01:00
char * Trim ( char * p ) {
// Remove leading and trailing tab, \n, \v, \f, \r and space
2020-08-04 15:33:05 +01:00
if ( * p ! = ' \0 ' ) {
2021-08-16 10:11:46 +01:00
while ( ( * p ! = ' \0 ' ) & & isspace ( * p ) ) { p + + ; } // Trim leading spaces
2020-08-04 15:33:05 +01:00
char * q = p + strlen ( p ) - 1 ;
2021-08-16 10:11:46 +01:00
while ( ( q > = p ) & & isspace ( * q ) ) { q - - ; } // Trim trailing spaces
2020-08-04 15:33:05 +01:00
q + + ;
* q = ' \0 ' ;
}
2018-05-29 16:24:42 +01:00
return p ;
}
2021-05-17 14:15:35 +01:00
String HexToString ( uint8_t * data , uint32_t length ) {
if ( ! data | | ! length ) { return " " ; }
uint32_t len = ( length < 16 ) ? length : 16 ;
char hex_data [ 32 ] ;
ToHex_P ( ( const unsigned char * ) data , len , hex_data , sizeof ( hex_data ) ) ;
String result = hex_data ;
result + = F ( " [ " ) ;
for ( uint32_t i = 0 ; i < len ; i + + ) {
result + = ( isprint ( data [ i ] ) ) ? ( char ) data [ i ] : ' ' ;
}
result + = F ( " ] " ) ;
if ( length > len ) {
result + = F ( " ... " ) ;
}
return result ;
}
2021-01-11 13:50:43 +00:00
String UrlEncode ( const String & text ) {
const char hex [ ] = " 0123456789ABCDEF " ;
String encoded = " " ;
int len = text . length ( ) ;
int i = 0 ;
while ( i < len ) {
char decodedChar = text . charAt ( i + + ) ;
/*
if ( ( ' a ' < = decodedChar & & decodedChar < = ' z ' ) | |
( ' A ' < = decodedChar & & decodedChar < = ' Z ' ) | |
( ' 0 ' < = decodedChar & & decodedChar < = ' 9 ' ) | |
( ' = ' = = decodedChar ) ) {
encoded + = decodedChar ;
} else {
encoded + = ' % ' ;
encoded + = hex [ decodedChar > > 4 ] ;
encoded + = hex [ decodedChar & 0xF ] ;
}
*/
if ( ( ' ' = = decodedChar ) | | ( ' + ' = = decodedChar ) ) {
encoded + = ' % ' ;
encoded + = hex [ decodedChar > > 4 ] ;
encoded + = hex [ decodedChar & 0xF ] ;
} else {
encoded + = decodedChar ;
}
}
return encoded ;
}
2018-04-20 16:43:20 +01:00
char * NoAlNumToUnderscore ( char * dest , const char * source )
2018-04-19 20:41:59 +01:00
{
char * write = dest ;
const char * read = source ;
char ch = ' . ' ;
while ( ch ! = ' \0 ' ) {
ch = * read + + ;
2018-04-20 16:43:20 +01:00
* write + + = ( isalnum ( ch ) | | ( ' \0 ' = = ch ) ) ? ch : ' _ ' ;
2018-04-19 20:41:59 +01:00
}
return dest ;
}
2019-11-20 19:53:12 +00:00
char IndexSeparator ( void )
2019-05-17 13:23:21 +01:00
{
/*
// 20 bytes more costly !?!
const char separators [ ] = { " -_ " } ;
2021-06-11 17:14:12 +01:00
return separators [ Settings - > flag3 . use_underscore ] ;
2019-05-17 13:23:21 +01:00
*/
2021-06-11 17:14:12 +01:00
if ( Settings - > flag3 . use_underscore ) { // SetOption64 - Enable "_" instead of "-" as sensor index separator
2019-05-17 13:23:21 +01:00
return ' _ ' ;
} else {
return ' - ' ;
}
}
2019-07-28 16:14:20 +01:00
void SetShortcutDefault ( void )
2018-08-27 13:53:09 +01:00
{
2019-07-28 16:14:20 +01:00
if ( ' \0 ' ! = XdrvMailbox . data [ 0 ] ) { // There must be at least one character in the buffer
XdrvMailbox . data [ 0 ] = ' 0 ' + SC_DEFAULT ; // SC_CLEAR, SC_DEFAULT, SC_USER
XdrvMailbox . data [ 1 ] = ' \0 ' ;
2018-08-27 13:53:09 +01:00
}
}
2019-11-20 19:53:12 +00:00
uint8_t Shortcut ( void )
2018-08-27 12:06:22 +01:00
{
uint8_t result = 10 ;
2019-07-28 16:14:20 +01:00
if ( ' \0 ' = = XdrvMailbox . data [ 1 ] ) { // Only allow single character input for shortcut
if ( ( ' " ' = = XdrvMailbox . data [ 0 ] ) | | ( ' 0 ' = = XdrvMailbox . data [ 0 ] ) ) {
2018-08-27 13:53:09 +01:00
result = SC_CLEAR ;
2018-08-27 12:06:22 +01:00
} else {
2019-07-28 16:14:20 +01:00
result = atoi ( XdrvMailbox . data ) ; // 1 = SC_DEFAULT, 2 = SC_USER
2018-08-27 12:06:22 +01:00
if ( 0 = = result ) {
result = 10 ;
}
}
}
return result ;
}
2019-03-31 16:57:28 +01:00
bool ValidIpAddress ( const char * str )
{
2021-01-18 15:32:58 +00:00
IPAddress ip_address ;
return ip_address . fromString ( str ) ;
2019-03-31 16:57:28 +01:00
}
2021-01-19 16:39:31 +00:00
bool ParseIPv4 ( uint32_t * addr , const char * str_p )
2017-03-25 16:24:11 +00:00
{
uint8_t * part = ( uint8_t * ) addr ;
2019-01-28 13:08:33 +00:00
uint8_t i ;
2021-01-18 20:48:04 +00:00
char str_r [ strlen_P ( str_p ) + 1 ] ;
char * str = & str_r [ 0 ] ;
strcpy_P ( str , str_p ) ;
2017-03-25 16:24:11 +00:00
* addr = 0 ;
for ( i = 0 ; i < 4 ; i + + ) {
2019-03-26 17:26:50 +00:00
part [ i ] = strtoul ( str , nullptr , 10 ) ; // Convert byte
2017-03-25 16:24:11 +00:00
str = strchr ( str , ' . ' ) ;
2019-03-26 17:26:50 +00:00
if ( str = = nullptr | | * str = = ' \0 ' ) {
2017-04-25 17:24:42 +01:00
break ; // No more separators, exit
}
2017-03-25 16:24:11 +00:00
str + + ; // Point to next character after separator
}
2017-04-25 17:24:42 +01:00
return ( 3 = = i ) ;
2017-03-25 16:24:11 +00:00
}
2022-01-01 13:38:13 +00:00
bool NewerVersion ( char * version_str ) {
// Function to parse & check if version_str is newer than our currently installed version.
2017-06-30 16:54:19 +01:00
uint32_t version = 0 ;
2019-07-27 17:37:56 +01:00
uint32_t i = 0 ;
2017-06-30 16:54:19 +01:00
char * str_ptr ;
2020-01-09 10:35:01 +00:00
char version_dup [ strlen ( version_str ) + 1 ] ;
strncpy ( version_dup , version_str , sizeof ( version_dup ) ) ; // Duplicate the version_str as strtok_r will modify it.
2017-06-30 16:54:19 +01:00
// Loop through the version string, splitting on '.' seperators.
2019-03-26 17:26:50 +00:00
for ( char * str = strtok_r ( version_dup , " . " , & str_ptr ) ; str & & i < sizeof ( VERSION ) ; str = strtok_r ( nullptr , " . " , & str_ptr ) , i + + ) {
2017-06-30 16:54:19 +01:00
int field = atoi ( str ) ;
2022-02-05 14:50:04 +00:00
// The fields in a version string can only range from 0-255.
if ( ( field < 0 ) | | ( field > 255 ) ) {
return false ;
}
// Shuffle the accumulated bytes across, and add the new byte.
version = ( version < < 8 ) + field ;
// Check alpha delimiter after 1.2.3 only
if ( ( 2 = = i ) & & isalpha ( str [ strlen ( str ) - 1 ] ) ) {
field = str [ strlen ( str ) - 1 ] & 0x1f ;
2022-01-01 13:38:13 +00:00
version = ( version < < 8 ) + field ;
2022-02-05 14:50:04 +00:00
i + + ;
2017-06-30 16:54:19 +01:00
}
}
2022-02-05 14:50:04 +00:00
// A version string should have 2-4 fields. e.g. 1.2, 1.2.3, or 1.2.3a (= 1.2.3.1).
2017-06-30 16:54:19 +01:00
// If not, then don't consider it a valid version string.
if ( ( i < 2 ) | | ( i > sizeof ( VERSION ) ) ) {
return false ;
}
// Keep shifting the parsed version until we hit the maximum number of tokens.
// VERSION stores the major number of the version in the most significant byte of the uint32_t.
while ( i < sizeof ( VERSION ) ) {
version < < = 8 ;
i + + ;
}
// Now we should have a fully constructed version number in uint32_t form.
return ( version > VERSION ) ;
}
2019-07-28 12:54:52 +01:00
char * GetPowerDevice ( char * dest , uint32_t idx , size_t size , uint32_t option )
2017-09-02 13:37:02 +01:00
{
2018-06-28 11:25:50 +01:00
strncpy_P ( dest , S_RSLT_POWER , size ) ; // POWER
2020-10-30 11:29:48 +00:00
if ( ( TasmotaGlobal . devices_present + option ) > 1 ) {
2019-10-30 13:08:43 +00:00
char sidx [ 8 ] ;
2018-06-28 11:25:50 +01:00
snprintf_P ( sidx , sizeof ( sidx ) , PSTR ( " %d " ) , idx ) ; // x
2018-11-22 14:41:30 +00:00
strncat ( dest , sidx , size - strlen ( dest ) - 1 ) ; // POWERx
2017-09-02 13:37:02 +01:00
}
return dest ;
}
2019-07-28 12:54:52 +01:00
char * GetPowerDevice ( char * dest , uint32_t idx , size_t size )
2017-09-02 13:37:02 +01:00
{
2017-10-18 17:22:34 +01:00
return GetPowerDevice ( dest , idx , size , 0 ) ;
2017-09-02 13:37:02 +01:00
}
2021-07-13 15:44:28 +01:00
float ConvertTempToFahrenheit ( float c ) {
2018-01-30 13:14:55 +00:00
float result = c ;
2021-06-11 17:14:12 +01:00
if ( ! isnan ( c ) & & Settings - > flag . temperature_conversion ) { // SetOption8 - Switch between Celsius or Fahrenheit
2022-04-19 13:45:26 +01:00
result = c * 1.8f + 32 ; // Fahrenheit
2018-01-30 13:14:55 +00:00
}
2022-04-19 13:45:26 +01:00
result = result + ( 0.1f * Settings - > temp_comp ) ;
2018-01-30 13:14:55 +00:00
return result ;
}
2021-07-13 15:44:28 +01:00
float ConvertTempToCelsius ( float c ) {
2019-04-18 10:07:38 +01:00
float result = c ;
2022-06-22 22:45:25 +01:00
if ( ! isnan ( c ) & & Settings - > flag . temperature_conversion ) { // SetOption8 - Switch between Celsius or Fahrenheit
2022-04-19 13:45:26 +01:00
result = ( c - 32 ) / 1.8f ; // Celsius
2019-04-18 10:07:38 +01:00
}
return result ;
}
2021-07-13 15:44:28 +01:00
void UpdateGlobalTemperature ( float c ) {
2022-06-22 22:45:25 +01:00
if ( ! Settings - > global_sensor_index [ 0 ] & & ! TasmotaGlobal . user_globals [ 0 ] ) {
TasmotaGlobal . global_update = TasmotaGlobal . uptime ;
TasmotaGlobal . temperature_celsius = c ;
}
2021-07-13 15:44:28 +01:00
}
float ConvertTemp ( float c ) {
UpdateGlobalTemperature ( c ) ;
return ConvertTempToFahrenheit ( c ) ;
}
2022-04-19 13:45:26 +01:00
char TempUnit ( void ) {
2020-05-07 23:53:13 +01:00
// SetOption8 - Switch between Celsius or Fahrenheit
2021-06-11 17:14:12 +01:00
return ( Settings - > flag . temperature_conversion ) ? D_UNIT_FAHRENHEIT [ 0 ] : D_UNIT_CELSIUS [ 0 ] ;
2018-01-30 13:14:55 +00:00
}
2022-04-19 13:45:26 +01:00
float ConvertHumidity ( float h ) {
2020-03-16 17:29:55 +00:00
float result = h ;
2022-06-22 22:45:25 +01:00
if ( ! Settings - > global_sensor_index [ 1 ] & & ! TasmotaGlobal . user_globals [ 1 ] ) {
TasmotaGlobal . global_update = TasmotaGlobal . uptime ;
TasmotaGlobal . humidity = h ;
}
2020-03-17 15:29:59 +00:00
2022-04-19 13:45:26 +01:00
result = result + ( 0.1f * Settings - > hum_comp ) ;
2019-04-15 17:12:42 +01:00
2020-03-16 17:29:55 +00:00
return result ;
2019-04-15 17:12:42 +01:00
}
2022-04-19 13:45:26 +01:00
float CalcTempHumToDew ( float t , float h ) {
2020-05-11 14:38:59 +01:00
if ( isnan ( h ) | | isnan ( t ) ) { return NAN ; }
2020-03-16 15:52:22 +00:00
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . temperature_conversion ) { // SetOption8 - Switch between Celsius or Fahrenheit
2022-04-19 13:45:26 +01:00
t = ( t - 32 ) / 1.8f ; // Celsius
2020-03-16 15:52:22 +00:00
}
2022-04-19 13:45:26 +01:00
float gamma = TaylorLog ( h / 100 ) + 17.62f * t / ( 243.5f + t ) ;
float result = ( 243.5f * gamma / ( 17.62f - gamma ) ) ;
2020-03-16 15:52:22 +00:00
2021-06-11 17:14:12 +01:00
if ( Settings - > flag . temperature_conversion ) { // SetOption8 - Switch between Celsius or Fahrenheit
2022-04-19 13:45:26 +01:00
result = result * 1.8f + 32 ; // Fahrenheit
2020-03-16 15:52:22 +00:00
}
return result ;
}
2022-06-22 22:45:25 +01:00
float ConvertHgToHpa ( float p ) {
// Convert mmHg (or inHg) to hPa
float result = p ;
if ( ! isnan ( p ) & & Settings - > flag . pressure_conversion ) { // SetOption24 - Switch between hPa or mmHg pressure unit
if ( Settings - > flag5 . mm_vs_inch ) { // SetOption139 - Switch between mmHg or inHg pressure unit
result = p * 33.86389f ; // inHg (double to float saves 16 bytes!)
} else {
result = p * 1.3332239f ; // mmHg (double to float saves 16 bytes!)
}
}
return result ;
}
2022-04-19 13:45:26 +01:00
float ConvertPressure ( float p ) {
2022-06-22 22:45:25 +01:00
// Convert hPa to mmHg (or inHg)
2018-11-01 15:36:22 +00:00
float result = p ;
2022-06-22 22:45:25 +01:00
if ( ! Settings - > global_sensor_index [ 2 ] & & ! TasmotaGlobal . user_globals [ 2 ] ) {
TasmotaGlobal . global_update = TasmotaGlobal . uptime ;
TasmotaGlobal . pressure_hpa = p ;
}
2019-04-15 17:12:42 +01:00
2022-04-19 13:45:26 +01:00
if ( ! isnan ( p ) & & Settings - > flag . pressure_conversion ) { // SetOption24 - Switch between hPa or mmHg pressure unit
if ( Settings - > flag5 . mm_vs_inch ) { // SetOption139 - Switch between mmHg or inHg pressure unit
// result = p * 0.02952998016471; // inHg
result = p * 0.0295299f ; // inHg (double to float saves 16 bytes!)
} else {
// result = p * 0.75006375541921; // mmHg
result = p * 0.7500637f ; // mmHg (double to float saves 16 bytes!)
}
2018-11-01 15:36:22 +00:00
}
return result ;
}
2022-04-19 13:45:26 +01:00
float ConvertPressureForSeaLevel ( float pressure ) {
if ( pressure = = 0.0f ) {
2020-10-29 09:34:44 +00:00
return pressure ;
2022-04-19 13:45:26 +01:00
}
2022-04-19 14:44:53 +01:00
return ConvertPressure ( ( pressure / FastPrecisePowf ( 1.0f - ( ( float ) Settings - > altitude / 44330.0f ) , 5.255f ) ) - 21.6f ) ;
2020-10-29 09:34:44 +00:00
}
2022-04-19 14:44:53 +01:00
const char kPressureUnit [ ] PROGMEM = D_UNIT_PRESSURE " | " D_UNIT_MILLIMETER_MERCURY " | " D_UNIT_INCH_MERCURY ;
String PressureUnit ( void ) {
uint32_t index = ( Settings - > flag . pressure_conversion ) ? Settings - > flag5 . mm_vs_inch + 1 : 0 ;
char text [ 8 ] ;
return String ( GetTextIndexed ( text , sizeof ( text ) , index , kPressureUnit ) ) ;
2018-11-04 15:55:12 +00:00
}
2020-03-02 14:51:33 +00:00
float ConvertSpeed ( float s )
{
// Entry in m/s
2021-06-11 17:14:12 +01:00
return s * kSpeedConversionFactor [ Settings - > flag2 . speed_conversion ] ;
2020-03-02 14:51:33 +00:00
}
2022-04-19 14:44:53 +01:00
String SpeedUnit ( void ) {
char text [ 8 ] ;
return String ( GetTextIndexed ( text , sizeof ( text ) , Settings - > flag2 . speed_conversion , kSpeedUnit ) ) ;
2020-03-02 14:51:33 +00:00
}
2018-11-14 13:32:09 +00:00
void ResetGlobalValues ( void )
2018-07-24 17:41:50 +01:00
{
2020-10-28 16:32:07 +00:00
if ( ( TasmotaGlobal . uptime - TasmotaGlobal . global_update ) > GLOBAL_VALUES_VALID ) { // Reset after 5 minutes
TasmotaGlobal . global_update = 0 ;
2020-10-28 18:03:39 +00:00
TasmotaGlobal . temperature_celsius = NAN ;
TasmotaGlobal . humidity = 0.0f ;
TasmotaGlobal . pressure_hpa = 0.0f ;
2018-07-24 17:41:50 +01:00
}
}
2018-09-28 14:48:42 +01:00
uint32_t SqrtInt ( uint32_t num )
{
if ( num < = 1 ) {
return num ;
}
uint32_t x = num / 2 ;
uint32_t y ;
do {
y = ( x + num / x ) / 2 ;
if ( y > = x ) {
return x ;
}
x = y ;
} while ( true ) ;
}
uint32_t RoundSqrtInt ( uint32_t num )
{
uint32_t s = SqrtInt ( 4 * num ) ;
if ( s & 1 ) {
s + + ;
}
return s / 2 ;
}
2019-07-27 17:37:56 +01:00
char * GetTextIndexed ( char * destination , size_t destination_size , uint32_t index , const char * haystack )
2018-01-30 13:14:55 +00:00
{
// Returns empty string if not found
// Returns text of found
char * write = destination ;
const char * read = haystack ;
index + + ;
while ( index - - ) {
size_t size = destination_size - 1 ;
write = destination ;
char ch = ' . ' ;
while ( ( ch ! = ' \0 ' ) & & ( ch ! = ' | ' ) ) {
ch = pgm_read_byte ( read + + ) ;
if ( size & & ( ch ! = ' | ' ) ) {
* write + + = ch ;
size - - ;
}
}
if ( 0 = = ch ) {
if ( index ) {
write = destination ;
}
break ;
}
}
* write = ' \0 ' ;
return destination ;
}
int GetCommandCode ( char * destination , size_t destination_size , const char * needle , const char * haystack )
{
// Returns -1 of not found
// Returns index and command if found
int result = - 1 ;
const char * read = haystack ;
char * write = destination ;
while ( true ) {
result + + ;
size_t size = destination_size - 1 ;
write = destination ;
char ch = ' . ' ;
while ( ( ch ! = ' \0 ' ) & & ( ch ! = ' | ' ) ) {
ch = pgm_read_byte ( read + + ) ;
if ( size & & ( ch ! = ' | ' ) ) {
* write + + = ch ;
size - - ;
}
}
* write = ' \0 ' ;
if ( ! strcasecmp ( needle , destination ) ) {
break ;
}
if ( 0 = = ch ) {
result = - 1 ;
break ;
}
}
return result ;
}
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
bool DecodeCommand ( const char * haystack , void ( * const MyCommand [ ] ) ( void ) , const uint8_t * synonyms = nullptr ) ;
bool DecodeCommand ( const char * haystack , void ( * const MyCommand [ ] ) ( void ) , const uint8_t * synonyms ) {
2019-08-11 17:12:18 +01:00
GetTextIndexed ( XdrvMailbox . command , CMDSZ , 0 , haystack ) ; // Get prefix if available
int prefix_length = strlen ( XdrvMailbox . command ) ;
2019-12-26 11:26:06 +00:00
if ( prefix_length ) {
char prefix [ prefix_length + 1 ] ;
snprintf_P ( prefix , sizeof ( prefix ) , XdrvMailbox . topic ) ; // Copy prefix part only
if ( strcasecmp ( prefix , XdrvMailbox . command ) ) {
return false ; // Prefix not in command
}
}
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
size_t syn_count = synonyms ? pgm_read_byte ( synonyms ) : 0 ;
2019-08-11 17:12:18 +01:00
int command_code = GetCommandCode ( XdrvMailbox . command + prefix_length , CMDSZ , XdrvMailbox . topic + prefix_length , haystack ) ;
if ( command_code > 0 ) { // Skip prefix
Add light synonyms
Add commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively
2021-01-26 13:56:58 +00:00
if ( command_code > syn_count ) {
// We passed the synonyms zone, it's a regular command
XdrvMailbox . command_code = command_code - 1 - syn_count ;
MyCommand [ XdrvMailbox . command_code ] ( ) ;
} else {
// We have a SetOption synonym
XdrvMailbox . index = pgm_read_byte ( synonyms + command_code ) ;
CmndSetoptionBase ( 0 ) ;
}
2019-08-13 15:10:47 +01:00
return true ;
2019-08-01 14:46:12 +01:00
}
2019-08-13 15:10:47 +01:00
return false ;
2019-07-28 16:57:09 +01:00
}
2022-12-10 22:54:09 +00:00
const char kOptions [ ] PROGMEM = " OFF| " D_OFF " |FALSE| " D_FALSE " |STOP| " D_STOP " | " D_CELSIUS " |DOWN| " D_CLOSE " | " // 0
2019-11-23 14:30:40 +00:00
" ON| " D_ON " |TRUE| " D_TRUE " |START| " D_START " | " D_FAHRENHEIT " | " D_USER " | " // 1
" TOGGLE| " D_TOGGLE " | " D_ADMIN " | " // 2
" BLINK| " D_BLINK " | " // 3
" BLINKOFF| " D_BLINKOFF " | " // 4
2023-01-07 14:37:52 +00:00
" UP| " D_OPEN " | " // 100
2019-11-23 14:30:40 +00:00
" ALL " ; // 255
2022-12-10 22:54:09 +00:00
const uint8_t sNumbers [ ] PROGMEM = { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
2019-11-23 14:30:40 +00:00
1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
2019-09-12 11:32:33 +01:00
2 , 2 , 2 ,
3 , 3 ,
4 , 4 ,
2022-12-10 22:54:09 +00:00
100 , 100 ,
2019-09-12 11:32:33 +01:00
255 } ;
2021-08-13 15:35:24 +01:00
int GetStateNumber ( const char * state_text )
2018-05-06 15:07:42 +01:00
{
char command [ CMDSZ ] ;
2019-09-12 11:32:33 +01:00
int state_number = GetCommandCode ( command , sizeof ( command ) , state_text , kOptions ) ;
if ( state_number > = 0 ) {
state_number = pgm_read_byte ( sNumbers + state_number ) ;
2018-05-06 15:07:42 +01:00
}
return state_number ;
}
2018-02-20 15:19:48 +00:00
uint32_t GetHash ( const char * buffer , size_t size )
{
uint32_t hash = 0 ;
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < = size ; i + + ) {
2018-02-20 15:19:48 +00:00
hash + = ( uint8_t ) * buffer + + * ( i + 1 ) ;
}
return hash ;
}
2019-07-27 10:13:41 +01:00
void ShowSource ( uint32_t source )
2018-05-28 14:52:42 +01:00
{
if ( ( source > 0 ) & & ( source < SRC_MAX ) ) {
char stemp1 [ 20 ] ;
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " SRC: %s " ) , GetTextIndexed ( stemp1 , sizeof ( stemp1 ) , source , kCommandSource ) ) ;
2018-05-28 14:52:42 +01:00
}
}
2019-07-28 12:54:52 +01:00
void WebHexCode ( uint32_t i , const char * code )
2019-04-09 12:56:19 +01:00
{
char scolor [ 10 ] ;
strlcpy ( scolor , code , sizeof ( scolor ) ) ;
char * p = scolor ;
if ( ' # ' = = p [ 0 ] ) { p + + ; } // Skip
if ( 3 = = strlen ( p ) ) { // Convert 3 character to 6 character color code
p [ 6 ] = p [ 3 ] ; // \0
p [ 5 ] = p [ 2 ] ; // 3
p [ 4 ] = p [ 2 ] ; // 3
p [ 3 ] = p [ 1 ] ; // 2
p [ 2 ] = p [ 1 ] ; // 2
p [ 1 ] = p [ 0 ] ; // 1
}
uint32_t color = strtol ( p , nullptr , 16 ) ;
/*
if ( 3 = = strlen ( p ) ) { // Convert 3 character to 6 character color code
uint32_t w = ( ( color & 0xF00 ) < < 8 ) | ( ( color & 0x0F0 ) < < 4 ) | ( color & 0x00F ) ; // 00010203
color = w | ( w < < 4 ) ; // 00112233
}
*/
2021-06-11 17:14:12 +01:00
uint32_t j = sizeof ( Settings - > web_color ) / 3 ; // First area contains j = 18 colors
2019-11-02 12:25:23 +00:00
/*
if ( i < j ) {
2021-06-11 17:14:12 +01:00
Settings - > web_color [ i ] [ 0 ] = ( color > > 16 ) & 0xFF ; // Red
Settings - > web_color [ i ] [ 1 ] = ( color > > 8 ) & 0xFF ; // Green
Settings - > web_color [ i ] [ 2 ] = color & 0xFF ; // Blue
2019-11-02 04:33:40 +00:00
} else {
2021-06-11 17:14:12 +01:00
Settings - > web_color2 [ i - j ] [ 0 ] = ( color > > 16 ) & 0xFF ; // Red
Settings - > web_color2 [ i - j ] [ 1 ] = ( color > > 8 ) & 0xFF ; // Green
Settings - > web_color2 [ i - j ] [ 2 ] = color & 0xFF ; // Blue
2019-11-02 12:25:23 +00:00
}
*/
if ( i > = j ) {
2021-06-11 17:14:12 +01:00
// Calculate i to index in Settings->web_color2 - Dirty(!) but saves 128 bytes code
i + = ( ( ( ( uint8_t * ) & Settings - > web_color2 - ( uint8_t * ) & Settings - > web_color ) / 3 ) - j ) ;
2019-11-02 12:25:23 +00:00
}
2021-06-11 17:14:12 +01:00
Settings - > web_color [ i ] [ 0 ] = ( color > > 16 ) & 0xFF ; // Red
Settings - > web_color [ i ] [ 1 ] = ( color > > 8 ) & 0xFF ; // Green
Settings - > web_color [ i ] [ 2 ] = color & 0xFF ; // Blue
2019-04-09 12:56:19 +01:00
}
2019-07-28 12:54:52 +01:00
uint32_t WebColor ( uint32_t i )
2019-04-09 12:56:19 +01:00
{
2021-06-11 17:14:12 +01:00
uint32_t j = sizeof ( Settings - > web_color ) / 3 ; // First area contains j = 18 colors
2019-11-02 12:25:23 +00:00
/*
2021-06-11 17:14:12 +01:00
uint32_t tcolor = ( i < j ) ? ( Settings - > web_color [ i ] [ 0 ] < < 16 ) | ( Settings - > web_color [ i ] [ 1 ] < < 8 ) | Settings - > web_color [ i ] [ 2 ] :
( Settings - > web_color2 [ i - j ] [ 0 ] < < 16 ) | ( Settings - > web_color2 [ i - j ] [ 1 ] < < 8 ) | Settings - > web_color2 [ i - j ] [ 2 ] ;
2019-11-02 12:25:23 +00:00
*/
if ( i > = j ) {
2021-06-11 17:14:12 +01:00
// Calculate i to index in Settings->web_color2 - Dirty(!) but saves 128 bytes code
i + = ( ( ( ( uint8_t * ) & Settings - > web_color2 - ( uint8_t * ) & Settings - > web_color ) / 3 ) - j ) ;
2019-11-02 12:25:23 +00:00
}
2021-06-11 17:14:12 +01:00
uint32_t tcolor = ( Settings - > web_color [ i ] [ 0 ] < < 16 ) | ( Settings - > web_color [ i ] [ 1 ] < < 8 ) | Settings - > web_color [ i ] [ 2 ] ;
2019-11-02 12:25:23 +00:00
2019-04-09 12:56:19 +01:00
return tcolor ;
}
2023-01-07 14:37:52 +00:00
void AllowInterrupts ( bool state ) {
if ( ! state ) { // Stop interrupts
XdrvXsnsCall ( FUNC_INTERRUPT_STOP ) ;
# ifdef USE_EMULATION
UdpDisconnect ( ) ;
# endif // USE_EMULATION
} else { // Start interrupts
# ifdef USE_EMULATION
UdpConnect ( ) ;
# endif // USE_EMULATION
XdrvXsnsCall ( FUNC_INTERRUPT_START ) ;
}
}
2019-03-23 16:00:59 +00:00
/*********************************************************************************************\
* Response data handling
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2019-09-04 17:06:34 +01:00
const uint16_t TIMESZ = 100 ; // Max number of characters in time string
char * ResponseGetTime ( uint32_t format , char * time_str )
{
switch ( format ) {
case 1 :
2019-09-06 14:46:40 +01:00
snprintf_P ( time_str , TIMESZ , PSTR ( " { \" " D_JSON_TIME " \" : \" %s \" , \" Epoch \" :%u " ) , GetDateAndTime ( DT_LOCAL ) . c_str ( ) , UtcTime ( ) ) ;
2019-09-04 17:06:34 +01:00
break ;
case 2 :
snprintf_P ( time_str , TIMESZ , PSTR ( " { \" " D_JSON_TIME " \" :%u " ) , UtcTime ( ) ) ;
break ;
2020-05-25 10:44:17 +01:00
case 3 :
2020-06-11 18:13:08 +01:00
snprintf_P ( time_str , TIMESZ , PSTR ( " { \" " D_JSON_TIME " \" : \" %s \" " ) , GetDateAndTime ( DT_LOCAL_MILLIS ) . c_str ( ) ) ;
2020-05-25 10:44:17 +01:00
break ;
2019-09-04 17:06:34 +01:00
default :
2019-09-06 14:46:40 +01:00
snprintf_P ( time_str , TIMESZ , PSTR ( " { \" " D_JSON_TIME " \" : \" %s \" " ) , GetDateAndTime ( DT_LOCAL ) . c_str ( ) ) ;
2019-09-04 17:06:34 +01:00
}
return time_str ;
}
2021-08-15 16:08:31 +01:00
char * ResponseData ( void ) {
# ifdef MQTT_DATA_STRING
return ( char * ) TasmotaGlobal . mqtt_data . c_str ( ) ;
# else
return TasmotaGlobal . mqtt_data ;
# endif
}
2021-05-23 15:50:17 +01:00
uint32_t ResponseSize ( void ) {
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
2021-06-09 16:24:28 +01:00
return MAX_LOGSZ ; // Arbitratry max length satisfying full log entry
2021-06-06 16:26:01 +01:00
# else
2021-05-23 15:50:17 +01:00
return sizeof ( TasmotaGlobal . mqtt_data ) ;
2021-06-06 16:26:01 +01:00
# endif
2021-05-23 15:50:17 +01:00
}
2021-05-23 13:42:27 +01:00
uint32_t ResponseLength ( void ) {
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
return TasmotaGlobal . mqtt_data . length ( ) ;
# else
2021-05-23 13:42:27 +01:00
return strlen ( TasmotaGlobal . mqtt_data ) ;
2021-06-06 16:26:01 +01:00
# endif
2021-05-23 13:42:27 +01:00
}
2020-10-30 11:29:48 +00:00
void ResponseClear ( void ) {
2021-05-24 15:52:59 +01:00
// Reset string length to zero
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
2021-06-11 09:04:27 +01:00
TasmotaGlobal . mqtt_data = " " ;
// TasmotaGlobal.mqtt_data = (const char*) nullptr; // Doesn't work on ESP32 as strlen() (in MqttPublishPayload) will fail (for obvious reasons)
2021-06-06 16:26:01 +01:00
# else
2020-10-30 11:29:48 +00:00
TasmotaGlobal . mqtt_data [ 0 ] = ' \0 ' ;
2021-06-06 16:26:01 +01:00
# endif
2020-10-30 11:29:48 +00:00
}
2021-05-23 13:42:27 +01:00
void ResponseJsonStart ( void ) {
2021-05-24 15:52:59 +01:00
// Insert a JSON start bracket {
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
TasmotaGlobal . mqtt_data . setCharAt ( 0 , ' { ' ) ;
# else
2021-05-23 13:42:27 +01:00
TasmotaGlobal . mqtt_data [ 0 ] = ' { ' ;
2021-06-06 16:26:01 +01:00
# endif
2021-05-23 13:42:27 +01:00
}
2019-09-04 17:06:34 +01:00
int Response_P ( const char * format , . . . ) // Content send snprintf_P char data
2019-03-23 16:00:59 +00:00
{
// This uses char strings. Be aware of sending %% if % is needed
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
va_list arg ;
va_start ( arg , format ) ;
char * mqtt_data = ext_vsnprintf_malloc_P ( format , arg ) ;
va_end ( arg ) ;
if ( mqtt_data ! = nullptr ) {
TasmotaGlobal . mqtt_data = mqtt_data ;
free ( mqtt_data ) ;
} else {
TasmotaGlobal . mqtt_data = " " ;
}
return TasmotaGlobal . mqtt_data . length ( ) ;
# else
2019-03-24 13:23:20 +00:00
va_list args ;
va_start ( args , format ) ;
2021-05-23 15:50:17 +01:00
int len = ext_vsnprintf_P ( TasmotaGlobal . mqtt_data , ResponseSize ( ) , format , args ) ;
2019-03-24 13:23:20 +00:00
va_end ( args ) ;
2019-03-23 16:00:59 +00:00
return len ;
2021-06-06 16:26:01 +01:00
# endif
2019-03-23 16:00:59 +00:00
}
2019-09-04 17:06:34 +01:00
int ResponseTime_P ( const char * format , . . . ) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
char timestr [ 100 ] ;
2021-06-11 17:14:12 +01:00
TasmotaGlobal . mqtt_data = ResponseGetTime ( Settings - > flag2 . time_format , timestr ) ;
2021-06-06 16:26:01 +01:00
va_list arg ;
va_start ( arg , format ) ;
char * mqtt_data = ext_vsnprintf_malloc_P ( format , arg ) ;
va_end ( arg ) ;
if ( mqtt_data ! = nullptr ) {
TasmotaGlobal . mqtt_data + = mqtt_data ;
free ( mqtt_data ) ;
}
return TasmotaGlobal . mqtt_data . length ( ) ;
# else
2019-09-04 17:06:34 +01:00
va_list args ;
va_start ( args , format ) ;
2021-06-11 17:14:12 +01:00
ResponseGetTime ( Settings - > flag2 . time_format , TasmotaGlobal . mqtt_data ) ;
2019-09-04 17:06:34 +01:00
2021-05-23 13:42:27 +01:00
int mlen = ResponseLength ( ) ;
2021-05-23 15:50:17 +01:00
int len = ext_vsnprintf_P ( TasmotaGlobal . mqtt_data + mlen , ResponseSize ( ) - mlen , format , args ) ;
2019-09-04 17:06:34 +01:00
va_end ( args ) ;
return len + mlen ;
2021-06-06 16:26:01 +01:00
# endif
2019-09-04 17:06:34 +01:00
}
2019-03-24 13:23:20 +00:00
int ResponseAppend_P ( const char * format , . . . ) // Content send snprintf_P char data
2019-03-23 16:00:59 +00:00
{
// This uses char strings. Be aware of sending %% if % is needed
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
va_list arg ;
va_start ( arg , format ) ;
char * mqtt_data = ext_vsnprintf_malloc_P ( format , arg ) ;
va_end ( arg ) ;
if ( mqtt_data ! = nullptr ) {
TasmotaGlobal . mqtt_data + = mqtt_data ;
free ( mqtt_data ) ;
}
return TasmotaGlobal . mqtt_data . length ( ) ;
# else
2019-03-24 13:23:20 +00:00
va_list args ;
va_start ( args , format ) ;
2021-05-23 13:42:27 +01:00
int mlen = ResponseLength ( ) ;
2021-05-23 15:50:17 +01:00
int len = ext_vsnprintf_P ( TasmotaGlobal . mqtt_data + mlen , ResponseSize ( ) - mlen , format , args ) ;
2019-03-24 13:23:20 +00:00
va_end ( args ) ;
2019-03-23 16:00:59 +00:00
return len + mlen ;
2021-06-06 16:26:01 +01:00
# endif
2019-03-23 16:00:59 +00:00
}
2019-09-04 17:06:34 +01:00
int ResponseAppendTimeFormat ( uint32_t format )
2019-07-11 13:09:42 +01:00
{
2019-09-04 17:06:34 +01:00
char time_str [ TIMESZ ] ;
return ResponseAppend_P ( ResponseGetTime ( format , time_str ) ) ;
2019-07-11 13:09:42 +01:00
}
2019-09-04 17:06:34 +01:00
int ResponseAppendTime ( void )
2019-07-11 13:09:42 +01:00
{
2021-06-11 17:14:12 +01:00
return ResponseAppendTimeFormat ( Settings - > flag2 . time_format ) ;
2019-07-11 13:09:42 +01:00
}
2020-03-17 15:29:59 +00:00
int ResponseAppendTHD ( float f_temperature , float f_humidity )
{
2021-01-24 15:35:36 +00:00
float dewpoint = CalcTempHumToDew ( f_temperature , f_humidity ) ;
return ResponseAppend_P ( PSTR ( " \" " D_JSON_TEMPERATURE " \" :%*_f, \" " D_JSON_HUMIDITY " \" :%*_f, \" " D_JSON_DEWPOINT " \" :%*_f " ) ,
2021-06-11 17:14:12 +01:00
Settings - > flag2 . temperature_resolution , & f_temperature ,
Settings - > flag2 . humidity_resolution , & f_humidity ,
Settings - > flag2 . temperature_resolution , & dewpoint ) ;
2020-03-17 15:29:59 +00:00
}
Add support for Shelly 1PM Template
Add support for Shelly 1PM Template {"NAME":"Shelly 1PM","GPIO":[56,0,0,0,82,134,0,0,0,0,0,21,0],"FLAG":2,"BASE":18} (#5716)
2019-05-13 17:26:07 +01:00
int ResponseJsonEnd ( void )
{
return ResponseAppend_P ( PSTR ( " } " ) ) ;
}
2019-09-12 13:19:44 +01:00
int ResponseJsonEndEnd ( void )
{
return ResponseAppend_P ( PSTR ( " }} " ) ) ;
}
2021-05-23 15:50:17 +01:00
bool ResponseContains_P ( const char * needle ) {
2021-08-15 16:08:31 +01:00
/*
2021-06-06 16:26:01 +01:00
# ifdef MQTT_DATA_STRING
return ( strstr_P ( TasmotaGlobal . mqtt_data . c_str ( ) , needle ) ! = nullptr ) ;
# else
2021-05-23 15:50:17 +01:00
return ( strstr_P ( TasmotaGlobal . mqtt_data , needle ) ! = nullptr ) ;
2021-06-06 16:26:01 +01:00
# endif
2021-07-13 15:44:28 +01:00
*/
2021-08-15 16:08:31 +01:00
return ( strstr_P ( ResponseData ( ) , needle ) ! = nullptr ) ;
}
2021-07-13 15:44:28 +01:00
2019-02-12 10:55:47 +00:00
/*********************************************************************************************\
* GPIO Module and Template management
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-09-25 17:34:14 +01:00
# ifdef ESP8266
2020-09-25 17:15:31 +01:00
uint16_t GpioConvert ( uint8_t gpio ) {
2021-02-28 11:50:02 +00:00
if ( gpio > = nitems ( kGpioConvert ) ) {
2020-09-25 17:15:31 +01:00
return AGPIO ( GPIO_USER ) ;
}
return pgm_read_word ( kGpioConvert + gpio ) ;
}
uint16_t Adc0Convert ( uint8_t adc0 ) {
if ( adc0 > 7 ) {
return AGPIO ( GPIO_USER ) ;
}
else if ( 0 = = adc0 ) {
return GPIO_NONE ;
}
return AGPIO ( GPIO_ADC_INPUT + adc0 - 1 ) ;
}
void TemplateConvert ( uint8_t template8 [ ] , uint16_t template16 [ ] ) {
for ( uint32_t i = 0 ; i < ( sizeof ( mytmplt ) / 2 ) - 2 ; i + + ) {
template16 [ i ] = GpioConvert ( template8 [ i ] ) ;
}
template16 [ ( sizeof ( mytmplt ) / 2 ) - 2 ] = Adc0Convert ( template8 [ sizeof ( mytmplt8285 ) - 1 ] ) ;
2020-09-29 13:08:48 +01:00
}
void ConvertGpios ( void ) {
2021-06-11 17:14:12 +01:00
if ( Settings - > gpio16_converted ! = 0xF5A0 ) {
2020-09-29 13:08:48 +01:00
// Convert 8-bit user template
2021-06-11 17:14:12 +01:00
TemplateConvert ( ( uint8_t * ) & Settings - > ex_user_template8 , ( uint16_t * ) & Settings - > user_template ) ;
2020-09-29 13:08:48 +01:00
2021-06-11 17:14:12 +01:00
for ( uint32_t i = 0 ; i < sizeof ( Settings - > ex_my_gp8 . io ) ; i + + ) {
Settings - > my_gp . io [ i ] = GpioConvert ( Settings - > ex_my_gp8 . io [ i ] ) ;
2020-09-29 13:08:48 +01:00
}
2021-06-11 17:14:12 +01:00
Settings - > my_gp . io [ ( sizeof ( myio ) / 2 ) - 1 ] = Adc0Convert ( Settings - > ex_my_adc0 ) ;
Settings - > gpio16_converted = 0xF5A0 ;
2020-09-30 13:19:18 +01:00
}
}
2020-09-25 17:34:14 +01:00
# endif // ESP8266
2020-09-25 17:15:31 +01:00
2021-04-02 16:14:08 +01:00
int IRAM_ATTR Pin ( uint32_t gpio , uint32_t index = 0 ) ;
int IRAM_ATTR Pin ( uint32_t gpio , uint32_t index ) {
2020-10-03 14:06:52 +01:00
uint16_t real_gpio = gpio < < 5 ;
uint16_t mask = 0xFFE0 ;
if ( index < GPIO_ANY ) {
real_gpio + = index ;
mask = 0xFFFF ;
}
2021-02-28 11:50:02 +00:00
for ( uint32_t i = 0 ; i < nitems ( TasmotaGlobal . gpio_pin ) ; i + + ) {
2020-10-29 12:58:50 +00:00
if ( ( TasmotaGlobal . gpio_pin [ i ] & mask ) = = real_gpio ) {
2020-04-26 16:33:27 +01:00
return i ; // Pin number configured for gpio
}
}
2021-02-14 12:06:19 +00:00
return - 1 ; // No pin used for gpio
2020-04-26 16:33:27 +01:00
}
2020-06-20 16:58:21 +01:00
bool PinUsed ( uint32_t gpio , uint32_t index = 0 ) ;
bool PinUsed ( uint32_t gpio , uint32_t index ) {
2021-02-15 16:26:55 +00:00
return ( Pin ( gpio , index ) > = 0 ) ;
2020-04-27 11:54:07 +01:00
}
2020-06-20 16:58:21 +01:00
uint32_t GetPin ( uint32_t lpin ) {
2021-02-28 11:50:02 +00:00
if ( lpin < nitems ( TasmotaGlobal . gpio_pin ) ) {
2020-10-29 12:58:50 +00:00
return TasmotaGlobal . gpio_pin [ lpin ] ;
2020-06-20 16:58:21 +01:00
} else {
return GPIO_NONE ;
}
}
2020-04-26 16:33:27 +01:00
void SetPin ( uint32_t lpin , uint32_t gpio ) {
2020-10-29 12:58:50 +00:00
TasmotaGlobal . gpio_pin [ lpin ] = gpio ;
2020-04-27 16:28:05 +01:00
}
2022-05-19 17:02:05 +01:00
void DigitalWrite ( uint32_t gpio_pin , uint32_t index , uint32_t state ) {
2022-05-20 11:10:46 +01:00
static uint32_t pinmode_init [ 2 ] = { 0 } ; // Pins 0 to 63 !!!
2022-05-20 10:38:11 +01:00
2020-04-27 11:54:07 +01:00
if ( PinUsed ( gpio_pin , index ) ) {
2022-05-20 11:10:46 +01:00
uint32_t pin = Pin ( gpio_pin , index ) & 0x3F ; // Fix possible overflow over 63 gpios
if ( ! bitRead ( pinmode_init [ pin / 32 ] , pin % 32 ) ) {
bitSet ( pinmode_init [ pin / 32 ] , pin % 32 ) ;
2022-05-20 10:38:11 +01:00
pinMode ( pin , OUTPUT ) ;
}
2022-05-19 17:02:05 +01:00
digitalWrite ( pin , state & 1 ) ;
2019-12-18 17:21:10 +00:00
}
}
2019-11-20 19:53:12 +00:00
uint8_t ModuleNr ( void )
2019-02-22 11:11:15 +00:00
{
// 0 = User module (255)
// 1 up = Template module 0 up
2021-06-11 17:14:12 +01:00
return ( USER_MODULE = = Settings - > module ) ? 0 : Settings - > module + 1 ;
2019-02-22 11:11:15 +00:00
}
2020-12-28 17:01:02 +00:00
uint32_t ModuleTemplate ( uint32_t module ) {
uint32_t i = 0 ;
for ( i = 0 ; i < sizeof ( kModuleNiceList ) ; i + + ) {
2020-12-29 16:42:53 +00:00
if ( module = = pgm_read_byte ( kModuleNiceList + i ) ) {
2020-12-28 17:01:02 +00:00
break ;
}
}
if ( i = = sizeof ( kModuleNiceList ) ) { i = 0 ; }
return i ;
}
2019-07-28 12:54:52 +01:00
bool ValidTemplateModule ( uint32_t index )
2019-04-05 14:27:06 +01:00
{
2019-06-30 15:44:36 +01:00
for ( uint32_t i = 0 ; i < sizeof ( kModuleNiceList ) ; i + + ) {
2019-04-05 14:27:06 +01:00
if ( index = = pgm_read_byte ( kModuleNiceList + i ) ) {
return true ;
}
}
return false ;
}
2019-07-28 12:54:52 +01:00
bool ValidModule ( uint32_t index )
2019-04-08 12:26:17 +01:00
{
if ( index = = USER_MODULE ) { return true ; }
return ValidTemplateModule ( index ) ;
}
2020-05-18 16:02:24 +01:00
bool ValidTemplate ( const char * search ) {
2021-04-26 09:35:39 +01:00
/*
2020-05-18 16:02:24 +01:00
char template_name [ strlen ( SettingsText ( SET_TEMPLATE_NAME ) ) + 1 ] ;
char search_name [ strlen ( search ) + 1 ] ;
LowerCase ( template_name , SettingsText ( SET_TEMPLATE_NAME ) ) ;
LowerCase ( search_name , search ) ;
return ( strstr ( template_name , search_name ) ! = nullptr ) ;
2021-04-26 09:35:39 +01:00
*/
2021-05-19 15:39:56 +01:00
return StrCaseStr_P ( SettingsText ( SET_TEMPLATE_NAME ) , search ) ;
2020-05-18 16:02:24 +01:00
}
2019-07-28 12:54:52 +01:00
String AnyModuleName ( uint32_t index )
2019-02-12 10:55:47 +00:00
{
if ( USER_MODULE = = index ) {
2020-03-29 16:41:31 +01:00
return String ( SettingsText ( SET_TEMPLATE_NAME ) ) ;
2019-02-12 10:55:47 +00:00
} else {
2020-12-29 16:42:53 +00:00
# ifdef ESP32
index = ModuleTemplate ( index ) ;
# endif
2020-03-29 16:41:31 +01:00
char name [ TOPSZ ] ;
2020-03-28 17:08:43 +00:00
return String ( GetTextIndexed ( name , sizeof ( name ) , index , kModuleNames ) ) ;
2019-02-12 10:55:47 +00:00
}
}
2019-11-20 19:53:12 +00:00
String ModuleName ( void )
2019-02-12 10:55:47 +00:00
{
2021-06-11 17:14:12 +01:00
return AnyModuleName ( Settings - > module ) ;
2019-02-12 10:55:47 +00:00
}
2020-08-23 08:08:47 +01:00
# ifdef ESP8266
2020-08-22 17:03:20 +01:00
void GetInternalTemplate ( void * ptr , uint32_t module , uint32_t option ) {
uint8_t module_template = pgm_read_byte ( kModuleTemplateList + module ) ;
2021-01-23 15:26:23 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: Template %d, Option %d"), module_template, option);
2020-08-22 17:03:20 +01:00
2020-09-25 17:15:31 +01:00
// template8 = GPIO 0,1,2,3,4,5,9,10,12,13,14,15,16,Adc
uint8_t template8 [ sizeof ( mytmplt8285 ) ] = { GPIO_NONE } ;
2020-08-22 17:03:20 +01:00
if ( module_template < TMP_WEMOS ) {
2020-09-25 17:15:31 +01:00
memcpy_P ( & template8 , & kModules8266 [ module_template ] , 6 ) ;
memcpy_P ( & template8 [ 8 ] , & kModules8266 [ module_template ] . gp . io [ 6 ] , 6 ) ;
2020-08-22 17:03:20 +01:00
} else {
2020-09-25 17:15:31 +01:00
memcpy_P ( & template8 , & kModules8285 [ module_template - TMP_WEMOS ] , sizeof ( template8 ) ) ;
2020-08-22 17:03:20 +01:00
}
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: GetInternalTemplate %*_H"), sizeof(mytmplt8285), (uint8_t *)&template8);
2020-09-25 17:15:31 +01:00
// template16 = GPIO 0,1,2,3,4,5,9,10,12,13,14,15,16,Adc,Flg
uint16_t template16 [ ( sizeof ( mytmplt ) / 2 ) ] = { GPIO_NONE } ;
TemplateConvert ( template8 , template16 ) ;
2020-08-22 17:03:20 +01:00
uint32_t index = 0 ;
2020-09-29 13:08:48 +01:00
uint32_t size = sizeof ( mycfgio ) ; // template16[module_template].gp
2020-08-22 17:03:20 +01:00
switch ( option ) {
case 2 : {
2020-09-29 13:08:48 +01:00
index = ( sizeof ( mytmplt ) / 2 ) - 1 ; // template16[module_template].flag
2020-09-25 17:15:31 +01:00
size = 2 ;
2020-08-22 17:03:20 +01:00
break ;
}
case 3 : {
2020-09-29 13:08:48 +01:00
size = sizeof ( mytmplt ) ; // template16[module_template]
2020-08-22 17:03:20 +01:00
break ;
}
}
2020-09-25 17:15:31 +01:00
memcpy ( ptr , & template16 [ index ] , size ) ;
2020-08-22 17:03:20 +01:00
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("FNC: GetInternalTemplate option %d, %*_V"), option, size / 2, (uint8_t *)ptr);
2020-08-22 17:03:20 +01:00
}
2020-08-23 08:08:47 +01:00
# endif // ESP8266
2020-08-22 17:03:20 +01:00
2021-09-05 18:43:53 +01:00
# ifdef CONFIG_IDF_TARGET_ESP32
// Conversion table from gpio template to physical gpio
const uint8_t Esp32TemplateToPhy [ MAX_USER_PINS ] = { ESP32_TEMPLATE_TO_PHY } ;
# endif // CONFIG_IDF_TARGET_ESP32
2020-11-24 14:14:22 +00:00
void TemplateGpios ( myio * gp )
2019-02-12 10:55:47 +00:00
{
2020-04-28 17:27:07 +01:00
uint16_t * dest = ( uint16_t * ) gp ;
2021-06-11 17:14:12 +01:00
uint16_t src [ nitems ( Settings - > user_template . gp . io ) ] ;
2019-02-12 10:55:47 +00:00
2020-04-24 16:39:26 +01:00
memset ( dest , GPIO_NONE , sizeof ( myio ) ) ;
2021-06-11 17:14:12 +01:00
if ( USER_MODULE = = Settings - > module ) {
memcpy ( & src , & Settings - > user_template . gp , sizeof ( mycfgio ) ) ;
2019-02-12 10:55:47 +00:00
} else {
2020-04-17 15:17:01 +01:00
# ifdef ESP8266
2021-06-11 17:14:12 +01:00
GetInternalTemplate ( & src , Settings - > module , 1 ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP8266
# ifdef ESP32
2021-06-11 17:14:12 +01:00
memcpy_P ( & src , & kModules [ ModuleTemplate ( Settings - > module ) ] . gp , sizeof ( mycfgio ) ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP32
2019-02-12 10:55:47 +00:00
}
// 11 85 00 85 85 00 00 00 15 38 85 00 00 81
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: TemplateGpiosIn %*_H"), sizeof(mycfgio), (uint8_t *)&src);
2019-02-12 10:55:47 +00:00
2021-09-05 18:43:53 +01:00
// Expand template to physical GPIO array, j=phy_GPIO, i=template_GPIO
2019-07-28 12:54:52 +01:00
uint32_t j = 0 ;
2021-06-11 17:14:12 +01:00
for ( uint32_t i = 0 ; i < nitems ( Settings - > user_template . gp . io ) ; i + + ) {
2021-06-08 19:31:01 +01:00
# if defined(ESP32) && CONFIG_IDF_TARGET_ESP32C3
2021-06-14 20:32:07 +01:00
dest [ i ] = src [ i ] ;
2022-02-01 11:05:56 +00:00
# elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
2021-09-04 13:20:09 +01:00
if ( 22 = = i ) { j = 33 ; } // skip 22-32
dest [ j ] = src [ i ] ;
j + + ;
2021-09-05 18:43:53 +01:00
# elif defined(CONFIG_IDF_TARGET_ESP32)
dest [ Esp32TemplateToPhy [ i ] ] = src [ i ] ;
# else // ESP8266
2019-02-12 10:55:47 +00:00
if ( 6 = = i ) { j = 9 ; }
if ( 8 = = i ) { j = 12 ; }
dest [ j ] = src [ i ] ;
j + + ;
2021-06-14 20:32:07 +01:00
# endif
2019-02-12 10:55:47 +00:00
}
// 11 85 00 85 85 00 00 00 00 00 00 00 15 38 85 00 00 81
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: TemplateGpiosOut %*_H"), sizeof(myio), (uint8_t *)gp);
2019-02-12 10:55:47 +00:00
}
2019-11-20 19:53:12 +00:00
gpio_flag ModuleFlag ( void )
2019-02-12 10:55:47 +00:00
{
gpio_flag flag ;
2021-06-11 17:14:12 +01:00
if ( USER_MODULE = = Settings - > module ) {
flag = Settings - > user_template . flag ;
2019-02-12 10:55:47 +00:00
} else {
2020-05-01 15:47:41 +01:00
# ifdef ESP8266
2021-06-11 17:14:12 +01:00
GetInternalTemplate ( & flag , Settings - > module , 2 ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP8266
# ifdef ESP32
2021-06-11 17:14:12 +01:00
memcpy_P ( & flag , & kModules [ ModuleTemplate ( Settings - > module ) ] . flag , sizeof ( gpio_flag ) ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP32
2020-05-01 15:47:41 +01:00
}
2019-02-12 10:55:47 +00:00
return flag ;
}
2019-07-28 12:54:52 +01:00
void ModuleDefault ( uint32_t module )
2019-02-12 10:55:47 +00:00
{
if ( USER_MODULE = = module ) { module = WEMOS ; } // Generic
2021-06-11 17:14:12 +01:00
Settings - > user_template_base = module ;
2020-12-29 16:42:53 +00:00
# ifdef ESP32
module = ModuleTemplate ( module ) ;
# endif
2020-03-29 16:41:31 +01:00
char name [ TOPSZ ] ;
SettingsUpdateText ( SET_TEMPLATE_NAME , GetTextIndexed ( name , sizeof ( name ) , module , kModuleNames ) ) ;
2020-04-17 15:17:01 +01:00
# ifdef ESP8266
2021-06-11 17:14:12 +01:00
GetInternalTemplate ( & Settings - > user_template , module , 3 ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP8266
# ifdef ESP32
2021-06-11 17:14:12 +01:00
memcpy_P ( & Settings - > user_template , & kModules [ module ] , sizeof ( mytmplt ) ) ;
2020-11-28 15:39:15 +00:00
# endif // ESP32
2019-02-12 10:55:47 +00:00
}
2019-11-20 19:53:12 +00:00
void SetModuleType ( void )
2019-02-12 10:55:47 +00:00
{
2021-06-11 17:14:12 +01:00
TasmotaGlobal . module_type = ( USER_MODULE = = Settings - > module ) ? Settings - > user_template_base : Settings - > module ;
2022-01-07 15:17:53 +00:00
# ifdef ESP32
if ( TasmotaGlobal . emulated_module_type ) {
TasmotaGlobal . module_type = TasmotaGlobal . emulated_module_type ;
}
# endif
2019-02-12 10:55:47 +00:00
}
2019-10-10 11:26:00 +01:00
bool FlashPin ( uint32_t pin )
{
2021-06-08 19:31:01 +01:00
# if defined(ESP32) && CONFIG_IDF_TARGET_ESP32C3
2022-08-13 11:53:01 +01:00
return ( ( ( pin > 10 ) & & ( pin < 12 ) ) | | ( ( pin > 13 ) & & ( pin < 18 ) ) ) ; // ESP32C3 has GPIOs 11-17 reserved for Flash, with some boards GPIOs 12 13 are useable
2022-02-01 11:05:56 +00:00
# elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
2021-09-04 13:20:09 +01:00
return ( pin > 21 ) & & ( pin < 33 ) ; // ESP32S2 skip 22-32
2021-09-05 18:43:53 +01:00
# elif defined(CONFIG_IDF_TARGET_ESP32)
return ( pin > = 28 ) & & ( pin < = 31 ) ; // ESP21 skip 28-31
# else // ESP8266
2019-10-10 11:26:00 +01:00
return ( ( ( pin > 5 ) & & ( pin < 9 ) ) | | ( 11 = = pin ) ) ;
2021-06-08 19:31:01 +01:00
# endif
}
bool RedPin ( uint32_t pin ) // pin may be dangerous to change, display in RED in template console
{
# if defined(ESP32) && CONFIG_IDF_TARGET_ESP32C3
2022-08-13 11:53:01 +01:00
return ( 12 = = pin ) | | ( 13 = = pin ) ; // ESP32C3: GPIOs 12 13 are usually used for Flash (mode QIO/QOUT)
2021-09-04 13:20:09 +01:00
# elif defined(CONFIG_IDF_TARGET_ESP32S2)
return false ; // no red pin on ESP32S3
2022-02-01 11:05:56 +00:00
# elif defined(CONFIG_IDF_TARGET_ESP32S3)
return ( 33 < = pin ) & & ( 37 > = pin ) ; // ESP32S3: GPIOs 33..37 are usually used for PSRAM
2021-09-05 18:43:53 +01:00
# elif defined(CONFIG_IDF_TARGET_ESP32) // red pins are 6-11 for original ESP32, other models like PICO are not impacted if flash pins are condfigured
// PICO can also have 16/17/18/23 not available
return ( ( 6 < = pin ) & & ( 11 > = pin ) ) | | ( 16 = = pin ) | | ( 17 = = pin ) ; // TODO adapt depending on the exact type of ESP32
# else // ESP8266
2021-06-08 19:31:01 +01:00
return ( 9 = = pin ) | | ( 10 = = pin ) ;
# endif
2019-10-10 11:26:00 +01:00
}
2020-12-31 15:17:30 +00:00
uint32_t ValidPin ( uint32_t pin , uint32_t gpio ) {
2019-10-10 11:26:00 +01:00
if ( FlashPin ( pin ) ) {
2019-12-02 09:31:33 +00:00
return GPIO_NONE ; // Disable flash pins GPIO6, GPIO7, GPIO8 and GPIO11
2019-02-12 10:55:47 +00:00
}
2019-12-02 09:31:33 +00:00
2021-09-04 13:20:09 +01:00
# if defined(CONFIG_IDF_TARGET_ESP32C3)
// ignore
# elif defined(CONFIG_IDF_TARGET_ESP32S2)
// ignore
2021-09-05 18:43:53 +01:00
# elif defined(CONFIG_IDF_TARGET_ESP32)
// ignore
2021-09-04 13:20:09 +01:00
# else // not ESP32C3 and not ESP32S2
2021-06-11 17:14:12 +01:00
if ( ( WEMOS = = Settings - > module ) & & ! Settings - > flag3 . user_esp8285_enable ) { // SetOption51 - Enable ESP8285 user GPIO's
2020-09-29 13:08:48 +01:00
if ( ( 9 = = pin ) | | ( 10 = = pin ) ) {
2019-12-02 09:31:33 +00:00
return GPIO_NONE ; // Disable possible flash GPIO9 and GPIO10
2019-11-21 14:00:35 +00:00
}
2019-02-12 10:55:47 +00:00
}
2021-09-04 13:20:09 +01:00
# endif
2019-11-21 14:00:35 +00:00
2019-12-02 09:31:33 +00:00
return gpio ;
2019-02-12 10:55:47 +00:00
}
2020-12-31 15:17:30 +00:00
bool ValidGPIO ( uint32_t pin , uint32_t gpio ) {
2020-09-29 13:08:48 +01:00
# ifdef ESP8266
# ifdef USE_ADC_VCC
2020-09-29 13:41:45 +01:00
if ( ADC0_PIN = = pin ) { return false ; } // ADC0 = GPIO17
2020-09-29 13:08:48 +01:00
# endif
# endif
2020-06-24 14:58:56 +01:00
return ( GPIO_USER = = ValidPin ( pin , BGPIO ( gpio ) ) ) ; // Only allow GPIO_USER pins
2019-02-12 10:55:47 +00:00
}
2021-01-01 14:05:58 +00:00
bool ValidSpiPinUsed ( uint32_t gpio ) {
2020-12-31 15:17:30 +00:00
// ESP8266: If SPI pin selected chk if it's not one of the three Hardware SPI pins (12..14)
2021-01-01 14:05:58 +00:00
bool result = false ;
2020-12-31 15:17:30 +00:00
if ( PinUsed ( gpio ) ) {
2021-02-14 12:06:19 +00:00
int pin = Pin ( gpio ) ;
2020-12-31 15:17:30 +00:00
result = ( ( pin < 12 ) | | ( pin > 14 ) ) ;
}
return result ;
}
2020-09-21 20:49:32 +01:00
bool JsonTemplate ( char * dataBuf )
2019-02-21 13:31:31 +00:00
{
2020-09-29 17:10:21 +01:00
// Old: {"NAME":"Shelly 2.5","GPIO":[56,0,17,0,21,83,0,0,6,82,5,22,156],"FLAG":2,"BASE":18}
// New: {"NAME":"Shelly 2.5","GPIO":[320,0,32,0,224,193,0,0,640,192,608,225,3456,4736],"FLAG":0,"BASE":18}
Add command WebColor
* Add rule Http#Initialized
* Add command WebColor to change non-persistent GUI colors on the fly
Use a rule like:
rule3 on http#initialized do webcolor {"webcolor":["#eeeeee","#181818","#4f4f4f","#000000","#dddddd","#008000","#222222","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]} endon
or
rule3 on http#initialized do webcolor {"webcolor":["#eee","#181818","#4f4f4f","#000","#ddd","#008000","#222"]} endon
to make color changes persistent)
2019-04-08 21:37:39 +01:00
2021-06-05 10:47:09 +01:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("TPL: |%s|"), dataBuf);
2020-10-10 11:20:15 +01:00
Add command WebColor
* Add rule Http#Initialized
* Add command WebColor to change non-persistent GUI colors on the fly
Use a rule like:
rule3 on http#initialized do webcolor {"webcolor":["#eeeeee","#181818","#4f4f4f","#000000","#dddddd","#008000","#222222","#ff0000","#008000","#ffffff","#1fa3ec","#0e70a4","#d43535","#931f1f","#47c266","#5aaf6f","#ffffff","#999999","#000000"]} endon
or
rule3 on http#initialized do webcolor {"webcolor":["#eee","#181818","#4f4f4f","#000","#ddd","#008000","#222"]} endon
to make color changes persistent)
2019-04-08 21:37:39 +01:00
if ( strlen ( dataBuf ) < 9 ) { return false ; } // Workaround exception if empty JSON like {} - Needs checks
2020-09-23 18:38:24 +01:00
JsonParser parser ( ( char * ) dataBuf ) ;
JsonParserObject root = parser . getRootObject ( ) ;
2020-09-21 20:49:32 +01:00
if ( ! root ) { return false ; }
2019-02-21 13:31:31 +00:00
// All parameters are optional allowing for partial changes
2020-09-21 20:49:32 +01:00
JsonParserToken val = root [ PSTR ( D_JSON_NAME ) ] ;
if ( val ) {
SettingsUpdateText ( SET_TEMPLATE_NAME , val . getStr ( ) ) ;
2019-02-21 13:31:31 +00:00
}
2020-09-21 20:49:32 +01:00
JsonParserArray arr = root [ PSTR ( D_JSON_GPIO ) ] ;
if ( arr ) {
2020-06-18 11:55:10 +01:00
# ifdef ESP8266
2020-10-04 14:46:20 +01:00
bool old_template = false ;
uint8_t template8 [ sizeof ( mytmplt8285 ) ] = { GPIO_NONE } ;
if ( 13 = = arr . size ( ) ) { // Possible old template
uint32_t gpio = 0 ;
2021-02-28 11:50:02 +00:00
for ( uint32_t i = 0 ; i < nitems ( template8 ) - 1 ; i + + ) {
2020-10-04 14:46:20 +01:00
gpio = arr [ i ] . getUInt ( ) ;
if ( gpio > 255 ) { // New templates might have values above 255
break ;
}
template8 [ i ] = gpio ;
}
old_template = ( gpio < 256 ) ;
}
if ( old_template ) {
2020-09-30 14:26:46 +01:00
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " TPL: Converting template ... " ) ) ;
2020-09-30 14:26:46 +01:00
val = root [ PSTR ( D_JSON_FLAG ) ] ;
if ( val ) {
2021-02-28 11:50:02 +00:00
template8 [ nitems ( template8 ) - 1 ] = val . getUInt ( ) & 0x0F ;
2020-09-30 14:26:46 +01:00
}
2021-06-11 17:14:12 +01:00
TemplateConvert ( template8 , Settings - > user_template . gp . io ) ;
Settings - > user_template . flag . data = 0 ;
2020-09-30 14:26:46 +01:00
} else {
2020-06-18 11:55:10 +01:00
# endif
2021-06-11 17:14:12 +01:00
for ( uint32_t i = 0 ; i < nitems ( Settings - > user_template . gp . io ) ; i + + ) {
2020-10-04 11:46:06 +01:00
JsonParserToken val = arr [ i ] ;
if ( ! val ) { break ; }
uint16_t gpio = val . getUInt ( ) ;
2020-09-30 14:26:46 +01:00
if ( gpio = = ( AGPIO ( GPIO_NONE ) + 1 ) ) {
gpio = AGPIO ( GPIO_USER ) ;
}
2021-06-11 17:14:12 +01:00
Settings - > user_template . gp . io [ i ] = gpio ;
2020-09-30 14:26:46 +01:00
}
val = root [ PSTR ( D_JSON_FLAG ) ] ;
if ( val ) {
2021-06-11 17:14:12 +01:00
Settings - > user_template . flag . data = val . getUInt ( ) ;
2020-09-30 14:26:46 +01:00
}
2019-02-21 13:31:31 +00:00
}
2020-09-30 14:26:46 +01:00
# ifdef ESP8266
2019-02-21 13:31:31 +00:00
}
2020-09-30 14:26:46 +01:00
# endif
2020-09-21 20:49:32 +01:00
val = root [ PSTR ( D_JSON_BASE ) ] ;
if ( val ) {
uint32_t base = val . getUInt ( ) ;
2019-04-08 12:26:17 +01:00
if ( ( 0 = = base ) | | ! ValidTemplateModule ( base - 1 ) ) { base = 18 ; }
2021-06-11 17:14:12 +01:00
Settings - > user_template_base = base - 1 ; // Default WEMOS
2019-02-21 13:31:31 +00:00
}
2020-10-10 11:20:15 +01:00
2021-04-25 16:22:38 +01:00
val = root [ PSTR ( D_JSON_CMND ) ] ;
if ( val ) {
2021-06-11 17:14:12 +01:00
if ( ( USER_MODULE = = Settings - > module ) | | StrCaseStr_P ( val . getStr ( ) , PSTR ( D_CMND_MODULE " 0 " ) ) ) { // Only execute if current module = USER_MODULE = this template
2021-04-25 16:22:38 +01:00
char * backup_data = XdrvMailbox . data ;
2021-04-29 16:00:04 +01:00
XdrvMailbox . data = ( char * ) val . getStr ( ) ; // Backlog commands
ReplaceChar ( XdrvMailbox . data , ' | ' , ' ; ' ) ; // Support '|' as command separator for JSON backwards compatibility
2021-04-25 16:22:38 +01:00
uint32_t backup_data_len = XdrvMailbox . data_len ;
2021-04-29 16:00:04 +01:00
XdrvMailbox . data_len = 1 ; // Any data
2021-04-25 16:22:38 +01:00
uint32_t backup_index = XdrvMailbox . index ;
2021-04-29 16:00:04 +01:00
XdrvMailbox . index = 0 ; // Backlog0 - no delay
2021-04-25 16:22:38 +01:00
CmndBacklog ( ) ;
XdrvMailbox . index = backup_index ;
XdrvMailbox . data_len = backup_data_len ;
XdrvMailbox . data = backup_data ;
}
}
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("TPL: Converted %*_V"), sizeof(Settings->user_template) / 2, (uint8_t*)&Settings->user_template);
2020-10-10 11:20:15 +01:00
2019-02-21 13:31:31 +00:00
return true ;
}
2019-11-20 19:53:12 +00:00
void TemplateJson ( void )
2019-02-12 10:55:47 +00:00
{
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("TPL: Show %*_V"), sizeof(Settings->user_template) / 2, (uint8_t*)&Settings->user_template);
2020-10-10 11:20:15 +01:00
2020-03-29 16:41:31 +01:00
Response_P ( PSTR ( " { \" " D_JSON_NAME " \" : \" %s \" , \" " D_JSON_GPIO " \" :[ " ) , SettingsText ( SET_TEMPLATE_NAME ) ) ;
2021-06-11 17:14:12 +01:00
for ( uint32_t i = 0 ; i < nitems ( Settings - > user_template . gp . io ) ; i + + ) {
uint16_t gpio = Settings - > user_template . gp . io [ i ] ;
2020-06-18 11:55:10 +01:00
if ( gpio = = AGPIO ( GPIO_USER ) ) {
gpio = AGPIO ( GPIO_NONE ) + 1 ;
}
ResponseAppend_P ( PSTR ( " %s%d " ) , ( i > 0 ) ? " , " : " " , gpio ) ;
2019-02-12 10:55:47 +00:00
}
2021-06-11 17:14:12 +01:00
ResponseAppend_P ( PSTR ( " ], \" " D_JSON_FLAG " \" :%d, \" " D_JSON_BASE " \" :%d} " ) , Settings - > user_template . flag , Settings - > user_template_base + 1 ) ;
2019-02-12 10:55:47 +00:00
}
2021-02-15 16:22:06 +00:00
# if ( defined(USE_SCRIPT) && defined(SUPPORT_MQTT_EVENT) ) || defined (USE_DT_VARS)
/*********************************************************************************************\
* Parse json paylod with path
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// parser object, source keys, delimiter, float result or NULL, string result or NULL, string size
// return 1 if numeric 2 if string, else 0 = not found
uint32_t JsonParsePath ( JsonParserObject * jobj , const char * spath , char delim , float * nres , char * sres , uint32_t slen ) {
uint32_t res = 0 ;
const char * cp = spath ;
# ifdef DEBUG_JSON_PARSE_PATH
AddLog ( LOG_LEVEL_INFO , PSTR ( " JSON: parsing json key: %s from json: %s " ) , cp , jpath ) ;
# endif
JsonParserObject obj = * jobj ;
JsonParserObject lastobj = obj ;
2022-06-07 07:27:44 +01:00
char selem [ 64 ] ;
2021-02-15 16:22:06 +00:00
uint8_t aindex = 0 ;
String value = " " ;
while ( 1 ) {
// read next element
for ( uint32_t sp = 0 ; sp < sizeof ( selem ) - 1 ; sp + + ) {
if ( ! * cp | | * cp = = delim ) {
selem [ sp ] = 0 ;
cp + + ;
break ;
}
selem [ sp ] = * cp + + ;
}
# ifdef DEBUG_JSON_PARSE_PATH
AddLog ( LOG_LEVEL_INFO , PSTR ( " JSON: cmp current key: %s " ) , selem ) ;
# endif
// check for array
char * sp = strchr ( selem , ' [ ' ) ;
if ( sp ) {
* sp = 0 ;
aindex = atoi ( sp + 1 ) ;
}
// now check element
obj = obj [ selem ] ;
if ( ! obj . isValid ( ) ) {
# ifdef DEBUG_JSON_PARSE_PATH
AddLog ( LOG_LEVEL_INFO , PSTR ( " JSON: obj invalid: %s " ) , selem ) ;
# endif
JsonParserToken tok = lastobj [ selem ] ;
if ( tok . isValid ( ) ) {
if ( tok . isArray ( ) ) {
JsonParserArray array = JsonParserArray ( tok ) ;
value = array [ aindex ] . getStr ( ) ;
if ( array . isNum ( ) ) {
if ( nres ) * nres = tok . getFloat ( ) ;
res = 1 ;
} else {
res = 2 ;
}
} else {
value = tok . getStr ( ) ;
if ( tok . isNum ( ) ) {
if ( nres ) * nres = tok . getFloat ( ) ;
res = 1 ;
} else {
res = 2 ;
}
}
}
# ifdef DEBUG_JSON_PARSE_PATH
AddLog ( LOG_LEVEL_INFO , PSTR ( " JSON: token invalid: %s " ) , selem ) ;
# endif
break ;
}
if ( obj . isObject ( ) ) {
lastobj = obj ;
continue ;
}
if ( ! * cp ) break ;
}
if ( sres ) {
strlcpy ( sres , value . c_str ( ) , slen ) ;
}
return res ;
}
# endif // USE_SCRIPT
2021-09-20 13:31:00 +01:00
/*********************************************************************************************\
* Serial
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-11-04 16:14:34 +00:00
String GetSerialConfig ( uint8_t serial_config ) {
2021-09-20 13:31:00 +01:00
// Settings->serial_config layout
// b000000xx - 5, 6, 7 or 8 data bits
// b00000x00 - 1 or 2 stop bits
// b000xx000 - None, Even or Odd parity
const static char kParity [ ] PROGMEM = " NEOI " ;
char config [ 4 ] ;
2021-11-04 16:14:34 +00:00
config [ 0 ] = ' 5 ' + ( serial_config & 0x3 ) ;
config [ 1 ] = pgm_read_byte ( & kParity [ ( serial_config > > 3 ) & 0x3 ] ) ;
config [ 2 ] = ' 1 ' + ( ( serial_config > > 2 ) & 0x1 ) ;
2021-09-20 13:31:00 +01:00
config [ 3 ] = ' \0 ' ;
return String ( config ) ;
}
2021-11-04 16:14:34 +00:00
String GetSerialConfig ( void ) {
return GetSerialConfig ( Settings - > serial_config ) ;
}
int8_t ParseSerialConfig ( const char * pstr )
{
if ( strlen ( pstr ) < 3 )
return - 1 ;
int8_t serial_config = ( uint8_t ) atoi ( pstr ) ;
if ( serial_config < 5 | | serial_config > 8 )
return - 1 ;
serial_config - = 5 ;
char parity = ( pstr [ 1 ] & 0xdf ) ;
if ( ' E ' = = parity ) {
serial_config + = 0x08 ; // Even parity
}
else if ( ' O ' = = parity ) {
serial_config + = 0x10 ; // Odd parity
}
else if ( ' N ' ! = parity ) {
return - 1 ;
}
if ( ' 2 ' = = pstr [ 2 ] ) {
serial_config + = 0x04 ; // Stop bits 2
}
else if ( ' 1 ' ! = pstr [ 2 ] ) {
return - 1 ;
}
return serial_config ;
}
2021-11-12 08:43:55 +00:00
uint32_t ConvertSerialConfig ( uint8_t serial_config ) {
2021-11-12 08:07:06 +00:00
# ifdef ESP8266
2021-11-12 08:43:55 +00:00
return ( uint32_t ) pgm_read_byte ( kTasmotaSerialConfig + serial_config ) ;
# elif defined(ESP32)
2021-11-12 08:07:06 +00:00
return ( uint32_t ) pgm_read_dword ( kTasmotaSerialConfig + serial_config ) ;
2021-11-12 08:43:55 +00:00
# else
# error "platform not supported"
# endif
2021-11-12 08:07:06 +00:00
}
2021-11-04 16:14:34 +00:00
2021-11-05 11:47:32 +00:00
// workaround disabled 05.11.2021 solved with https://github.com/espressif/arduino-esp32/pull/5549
//#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32C3
2021-09-20 13:31:00 +01:00
// temporary workaround, see https://github.com/espressif/arduino-esp32/issues/5287
2021-11-05 11:47:32 +00:00
//#include <driver/uart.h>
//uint32_t GetSerialBaudrate(void) {
// uint32_t br;
// uart_get_baudrate(0, &br);
// return (br / 300) * 300; // Fix ESP32 strange results like 115201
//}
//#else
2021-09-20 13:31:00 +01:00
uint32_t GetSerialBaudrate ( void ) {
return ( Serial . baudRate ( ) / 300 ) * 300 ; // Fix ESP32 strange results like 115201
}
2021-11-05 11:47:32 +00:00
//#endif
2021-09-20 13:31:00 +01:00
# ifdef ESP8266
void SetSerialSwap ( void ) {
if ( ( 15 = = Pin ( GPIO_TXD ) ) & & ( 13 = = Pin ( GPIO_RXD ) ) ) {
Serial . flush ( ) ;
Serial . swap ( ) ;
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_SERIAL " Serial pins swapped to alternate " ) ) ;
}
}
# endif
void SetSerialBegin ( void ) {
TasmotaGlobal . baudrate = Settings - > baudrate * 300 ;
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_SERIAL " Set to %s %d bit/s " ) , GetSerialConfig ( ) . c_str ( ) , TasmotaGlobal . baudrate ) ;
Serial . flush ( ) ;
# ifdef ESP8266
2021-12-30 13:39:22 +00:00
Serial . begin ( TasmotaGlobal . baudrate , ( SerialConfig ) ConvertSerialConfig ( Settings - > serial_config ) ) ;
2021-09-20 13:31:00 +01:00
SetSerialSwap ( ) ;
# endif // ESP8266
# ifdef ESP32
2022-04-25 11:03:53 +01:00
# ifdef ARDUINO_USB_CDC_ON_BOOT
// Serial.end();
// Serial.begin();
// Above sequence ends in "Exception":5,"Reason":"Load access fault"
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_SERIAL " HWCDC supports 115200 bit/s only " ) ) ;
# else
2021-09-20 13:31:00 +01:00
delay ( 10 ) ; // Allow time to cleanup queues - if not used hangs ESP32
Serial . end ( ) ;
delay ( 10 ) ; // Allow time to cleanup queues - if not used hangs ESP32
2021-12-30 13:39:22 +00:00
Serial . begin ( TasmotaGlobal . baudrate , ConvertSerialConfig ( Settings - > serial_config ) ) ;
2022-04-25 11:03:53 +01:00
# endif // Not ARDUINO_USB_CDC_ON_BOOT
2021-09-20 13:31:00 +01:00
# endif // ESP32
}
2022-11-02 10:24:24 +00:00
void SetSerialInitBegin ( void ) {
TasmotaGlobal . baudrate = Settings - > baudrate * 300 ;
if ( ( GetSerialBaudrate ( ) ! = TasmotaGlobal . baudrate ) | | ( TS_SERIAL_8N1 ! = Settings - > serial_config ) ) {
SetSerialBegin ( ) ;
}
}
2021-09-20 13:31:00 +01:00
void SetSerialConfig ( uint32_t serial_config ) {
if ( serial_config > TS_SERIAL_8O2 ) {
serial_config = TS_SERIAL_8N1 ;
}
if ( serial_config ! = Settings - > serial_config ) {
Settings - > serial_config = serial_config ;
SetSerialBegin ( ) ;
}
}
void SetSerialBaudrate ( uint32_t baudrate ) {
TasmotaGlobal . baudrate = baudrate ;
Settings - > baudrate = TasmotaGlobal . baudrate / 300 ;
if ( GetSerialBaudrate ( ) ! = TasmotaGlobal . baudrate ) {
SetSerialBegin ( ) ;
}
}
void SetSerial ( uint32_t baudrate , uint32_t serial_config ) {
Settings - > flag . mqtt_serial = 0 ; // CMND_SERIALSEND and CMND_SERIALLOG
Settings - > serial_config = serial_config ;
TasmotaGlobal . baudrate = baudrate ;
Settings - > baudrate = TasmotaGlobal . baudrate / 300 ;
SetSeriallog ( LOG_LEVEL_NONE ) ;
SetSerialBegin ( ) ;
}
void ClaimSerial ( void ) {
2022-10-10 13:21:25 +01:00
# ifdef ESP32
# if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
# ifdef USE_USB_CDC_CONSOLE
return ; // USB console does not use serial
# endif // USE_USB_CDC_CONSOLE
# endif // ESP32C3, S2 or S3
# endif // ESP32
2021-09-20 13:31:00 +01:00
TasmotaGlobal . serial_local = true ;
AddLog ( LOG_LEVEL_INFO , PSTR ( " SNS: Hardware Serial " ) ) ;
SetSeriallog ( LOG_LEVEL_NONE ) ;
TasmotaGlobal . baudrate = GetSerialBaudrate ( ) ;
Settings - > baudrate = TasmotaGlobal . baudrate / 300 ;
2022-10-10 13:21:25 +01:00
2021-09-20 13:31:00 +01:00
}
void SerialSendRaw ( char * codes )
{
char * p ;
char stemp [ 3 ] ;
uint8_t code ;
int size = strlen ( codes ) ;
while ( size > 1 ) {
strlcpy ( stemp , codes , sizeof ( stemp ) ) ;
code = strtol ( stemp , & p , 16 ) ;
Serial . write ( code ) ;
size - = 2 ;
codes + = 2 ;
}
}
// values is a comma-delimited string: e.g. "72,101,108,108,111,32,87,111,114,108,100,33,10"
void SerialSendDecimal ( char * values )
{
char * p ;
uint8_t code ;
for ( char * str = strtok_r ( values , " , " , & p ) ; str ; str = strtok_r ( nullptr , " , " , & p ) ) {
code = ( uint8_t ) atoi ( str ) ;
Serial . write ( code ) ;
}
}
2022-03-12 16:38:49 +00:00
/*********************************************************************************************/
uint8_t Bcd2Dec ( uint8_t n ) {
return n - 6 * ( n > > 4 ) ;
}
uint8_t Dec2Bcd ( uint8_t n ) {
return n + 6 * ( n / 10 ) ;
}
2022-04-03 17:20:07 +01:00
/*********************************************************************************************/
uint8_t TasShiftIn ( uint8_t dataPin , uint8_t clockPin , uint8_t bitOrder ) {
uint8_t value = 0 ;
for ( uint32_t i = 0 ; i < 8 ; + + i ) {
digitalWrite ( clockPin , HIGH ) ;
# ifdef ESP32
delayMicroseconds ( 1 ) ;
# endif
if ( bitOrder = = LSBFIRST ) {
value | = digitalRead ( dataPin ) < < i ;
} else {
value | = digitalRead ( dataPin ) < < ( 7 - i ) ;
}
digitalWrite ( clockPin , LOW ) ;
# ifdef ESP32
delayMicroseconds ( 1 ) ;
# endif
}
return value ;
}
void TasShiftOut ( uint8_t dataPin , uint8_t clockPin , uint8_t bitOrder , uint8_t val ) {
for ( uint32_t i = 0 ; i < 8 ; i + + ) {
if ( bitOrder = = LSBFIRST ) {
digitalWrite ( dataPin , ! ! ( val & ( 1 < < i ) ) ) ;
} else {
digitalWrite ( dataPin , ! ! ( val & ( 1 < < ( 7 - i ) ) ) ) ;
}
digitalWrite ( clockPin , HIGH ) ;
# ifdef ESP32
delayMicroseconds ( 1 ) ;
# endif
digitalWrite ( clockPin , LOW ) ;
# ifdef ESP32
delayMicroseconds ( 1 ) ;
# endif
}
}
2018-08-26 14:42:35 +01:00
/*********************************************************************************************\
* Sleep aware time scheduler functions borrowed from ESPEasy
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-02-15 11:57:23 +00:00
inline int32_t TimeDifference ( uint32_t prev , uint32_t next )
{
2020-01-14 15:41:47 +00:00
return ( ( int32_t ) ( next - prev ) ) ;
2018-08-26 14:42:35 +01:00
}
2020-01-14 15:41:47 +00:00
int32_t TimePassedSince ( uint32_t timestamp )
2018-08-26 14:42:35 +01:00
{
// Compute the number of milliSeconds passed since timestamp given.
// Note: value can be negative if the timestamp has not yet been reached.
return TimeDifference ( timestamp , millis ( ) ) ;
}
2020-01-14 15:41:47 +00:00
bool TimeReached ( uint32_t timer )
2018-08-26 14:42:35 +01:00
{
// Check if a certain timeout has been reached.
const long passed = TimePassedSince ( timer ) ;
return ( passed > = 0 ) ;
}
2020-10-28 10:48:57 +00:00
void SetNextTimeInterval ( uint32_t & timer , const uint32_t step )
2018-08-26 14:42:35 +01:00
{
timer + = step ;
const long passed = TimePassedSince ( timer ) ;
if ( passed < 0 ) { return ; } // Event has not yet happened, which is fine.
if ( static_cast < unsigned long > ( passed ) > step ) {
// No need to keep running behind, start again.
timer = millis ( ) + step ;
return ;
}
// Try to get in sync again.
timer = millis ( ) + ( step - passed ) ;
2018-05-17 14:36:45 +01:00
}
2020-02-15 11:57:23 +00:00
int32_t TimePassedSinceUsec ( uint32_t timestamp )
{
return TimeDifference ( timestamp , micros ( ) ) ;
}
bool TimeReachedUsec ( uint32_t timer )
{
// Check if a certain timeout has been reached.
const long passed = TimePassedSinceUsec ( timer ) ;
return ( passed > = 0 ) ;
}
2017-01-28 13:41:01 +00:00
/*********************************************************************************************\
* Syslog
2017-11-04 15:36:51 +00:00
*
2017-11-03 17:07:25 +00:00
* Example :
2021-01-23 15:26:23 +00:00
* AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_LOG " Any value %d " ) , value ) ;
2017-11-03 17:07:25 +00:00
*
2017-01-28 13:41:01 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-05-06 13:57:03 +01:00
void SetTasConlog ( uint32_t loglevel ) {
2021-06-11 17:14:12 +01:00
Settings - > seriallog_level = loglevel ;
2020-10-30 11:29:48 +00:00
TasmotaGlobal . seriallog_level = loglevel ;
2020-10-29 12:37:09 +00:00
TasmotaGlobal . seriallog_timer = 0 ;
2018-03-18 12:47:30 +00:00
}
2022-05-06 13:57:03 +01:00
void SetSeriallog ( uint32_t loglevel ) {
# ifdef ESP32
if ( tasconsole_serial ) {
# endif // ESP32
SetTasConlog ( loglevel ) ;
# ifdef ESP32
}
# endif // ESP32
}
2019-07-28 12:54:52 +01:00
void SetSyslog ( uint32_t loglevel )
2019-06-03 16:05:09 +01:00
{
2021-06-11 17:14:12 +01:00
Settings - > syslog_level = loglevel ;
2020-10-30 11:29:48 +00:00
TasmotaGlobal . syslog_level = loglevel ;
2020-10-29 12:37:09 +00:00
TasmotaGlobal . syslog_timer = 0 ;
2019-06-03 16:05:09 +01:00
}
2020-12-23 15:35:46 +00:00
void SyslogAsync ( bool refresh ) {
2020-12-19 16:29:14 +00:00
static IPAddress syslog_host_addr ; // Syslog host IP address
static uint32_t syslog_host_hash = 0 ; // Syslog host name hash
static uint32_t index = 1 ;
2020-12-18 14:37:20 +00:00
2021-03-18 08:27:46 +00:00
if ( ! TasmotaGlobal . syslog_level | | TasmotaGlobal . global_state . network_down ) { return ; }
2020-12-22 14:26:07 +00:00
if ( refresh & & ! NeedLogRefresh ( TasmotaGlobal . syslog_level , index ) ) { return ; }
2020-12-19 16:29:14 +00:00
char * line ;
size_t len ;
while ( GetLog ( TasmotaGlobal . syslog_level , & index , & line , & len ) ) {
2020-12-23 15:35:46 +00:00
// 00:00:02.096 HTP: Web server active on wemos5 with IP address 192.168.2.172
// HTP: Web server active on wemos5 with IP address 192.168.2.172
2020-12-20 16:15:22 +00:00
uint32_t mxtime = strchr ( line , ' ' ) - line + 1 ; // Remove mxtime
if ( mxtime > 0 ) {
2020-12-23 15:35:46 +00:00
uint32_t current_hash = GetHash ( SettingsText ( SET_SYSLOG_HOST ) , strlen ( SettingsText ( SET_SYSLOG_HOST ) ) ) ;
if ( syslog_host_hash ! = current_hash ) {
2021-03-16 21:28:39 +00:00
IPAddress temp_syslog_host_addr ;
2022-06-19 17:13:26 +01:00
if ( ! WifiHostByName ( SettingsText ( SET_SYSLOG_HOST ) , temp_syslog_host_addr ) ) { // If sleep enabled this might result in exception so try to do it once using hash
2021-03-17 18:45:29 +00:00
TasmotaGlobal . syslog_level = 0 ;
TasmotaGlobal . syslog_timer = SYSLOG_TIMER ;
2022-08-05 14:25:39 +01:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " SLG: " D_RETRY_IN " %d " D_UNIT_SECOND ) , SYSLOG_TIMER ) ;
2021-03-17 18:45:29 +00:00
return ;
}
2020-12-23 15:35:46 +00:00
syslog_host_hash = current_hash ;
2021-03-16 21:28:39 +00:00
syslog_host_addr = temp_syslog_host_addr ;
2020-12-23 15:35:46 +00:00
}
2021-06-11 17:14:12 +01:00
if ( ! PortUdp . beginPacket ( syslog_host_addr , Settings - > syslog_port ) ) {
2020-12-23 15:35:46 +00:00
TasmotaGlobal . syslog_level = 0 ;
TasmotaGlobal . syslog_timer = SYSLOG_TIMER ;
2022-08-05 14:25:39 +01:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " SLG: " D_SYSLOG_HOST_NOT_FOUND " . " D_RETRY_IN " %d " D_UNIT_SECOND ) , SYSLOG_TIMER ) ;
2020-12-24 10:42:59 +00:00
return ;
2020-12-23 15:35:46 +00:00
}
2021-05-30 17:47:39 +01:00
2021-05-31 13:23:12 +01:00
char header [ 64 ] ;
snprintf_P ( header , sizeof ( header ) , PSTR ( " %s ESP- " ) , NetworkHostname ( ) ) ;
char * line_start = line + mxtime ;
# ifdef ESP8266
// Packets over 1460 bytes are not send
uint32_t line_len ;
int32_t log_len = len - mxtime - 1 ;
while ( log_len > 0 ) {
PortUdp . write ( header ) ;
line_len = ( log_len > 1460 ) ? 1460 : log_len ;
PortUdp . write ( ( uint8_t * ) line_start , line_len ) ;
PortUdp . endPacket ( ) ;
log_len - = 1460 ;
line_start + = 1460 ;
}
# else
2022-01-14 21:53:19 +00:00
PortUdp . write ( ( const uint8_t * ) header , strlen ( header ) ) ;
2021-05-31 13:23:12 +01:00
PortUdp . write ( ( uint8_t * ) line_start , len - mxtime - 1 ) ;
2020-12-24 10:42:59 +00:00
PortUdp . endPacket ( ) ;
2021-05-31 13:23:12 +01:00
# endif
2020-12-24 10:42:59 +00:00
delay ( 1 ) ; // Add time for UDP handling (#5512)
2020-12-18 14:37:20 +00:00
}
2020-12-19 16:29:14 +00:00
}
}
2020-12-22 14:26:07 +00:00
bool NeedLogRefresh ( uint32_t req_loglevel , uint32_t index ) {
2021-06-11 15:02:49 +01:00
if ( ! TasmotaGlobal . log_buffer ) { return false ; } // Leave now if there is no buffer available
2021-01-01 16:04:36 +00:00
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
2020-12-31 11:36:35 +00:00
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
2021-09-06 18:52:20 +01:00
TasAutoMutex mutex ( ( SemaphoreHandle_t * ) & TasmotaGlobal . log_buffer_mutex ) ;
2021-01-01 16:04:36 +00:00
# endif // ESP32
2020-12-31 11:36:35 +00:00
2020-12-22 14:26:07 +00:00
// Skip initial buffer fill
2021-06-09 16:24:28 +01:00
if ( strlen ( TasmotaGlobal . log_buffer ) < LOG_BUFFER_SIZE / 2 ) { return false ; }
2020-12-22 14:26:07 +00:00
char * line ;
size_t len ;
if ( ! GetLog ( req_loglevel , & index , & line , & len ) ) { return false ; }
return ( ( line - TasmotaGlobal . log_buffer ) < LOG_BUFFER_SIZE / 4 ) ;
}
2020-12-19 16:29:14 +00:00
bool GetLog ( uint32_t req_loglevel , uint32_t * index_p , char * * entry_pp , size_t * len_p ) {
2021-06-11 15:02:49 +01:00
if ( ! TasmotaGlobal . log_buffer ) { return false ; } // Leave now if there is no buffer available
if ( TasmotaGlobal . uptime < 3 ) { return false ; } // Allow time to setup correct log level
2020-12-19 16:29:14 +00:00
2021-06-11 15:02:49 +01:00
uint32_t index = * index_p ;
2020-12-19 16:29:14 +00:00
if ( ! req_loglevel | | ( index = = TasmotaGlobal . log_buffer_pointer ) ) { return false ; }
2021-01-01 16:04:36 +00:00
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
2020-12-31 11:36:35 +00:00
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
2021-09-06 18:52:20 +01:00
TasAutoMutex mutex ( ( SemaphoreHandle_t * ) & TasmotaGlobal . log_buffer_mutex ) ;
2021-01-01 16:04:36 +00:00
# endif // ESP32
2020-12-31 11:36:35 +00:00
2020-12-19 16:29:14 +00:00
if ( ! index ) { // Dump all
2021-06-18 15:47:14 +01:00
index = TasmotaGlobal . log_buffer [ 0 ] ;
2020-12-19 16:29:14 +00:00
}
do {
size_t len = 0 ;
uint32_t loglevel = 0 ;
char * entry_p = TasmotaGlobal . log_buffer ;
2020-12-18 14:37:20 +00:00
do {
2020-12-19 16:29:14 +00:00
uint32_t cur_idx = * entry_p ;
entry_p + + ;
size_t tmp = strchrspn ( entry_p , ' \1 ' ) ;
2020-12-18 15:30:37 +00:00
tmp + + ; // Skip terminating '\1'
2020-12-19 16:29:14 +00:00
if ( cur_idx = = index ) { // Found the requested entry
loglevel = * entry_p - ' 0 ' ;
entry_p + + ; // Skip loglevel
2020-12-18 14:37:20 +00:00
len = tmp - 1 ;
break ;
}
2020-12-19 16:29:14 +00:00
entry_p + = tmp ;
} while ( entry_p < TasmotaGlobal . log_buffer + LOG_BUFFER_SIZE & & * entry_p ! = ' \0 ' ) ;
index + + ;
if ( index > 255 ) { index = 1 ; } // Skip 0 as it is not allowed
* index_p = index ;
if ( ( len > 0 ) & &
( loglevel < = req_loglevel ) & &
( TasmotaGlobal . masterlog_level < = req_loglevel ) ) {
* entry_pp = entry_p ;
* len_p = len ;
return true ;
}
delay ( 0 ) ;
} while ( index ! = TasmotaGlobal . log_buffer_pointer ) ;
return false ;
2020-12-18 14:37:20 +00:00
}
2021-06-10 17:06:11 +01:00
void AddLogData ( uint32_t loglevel , const char * log_data , const char * log_data_payload = nullptr , const char * log_data_retained = nullptr ) {
2021-09-02 11:25:53 +01:00
if ( ! TasmotaGlobal . enable_logging ) { return ; }
2021-06-10 17:06:11 +01:00
// Store log_data in buffer
// To lower heap usage log_data_payload may contain the payload data from MqttPublishPayload()
// and log_data_retained may contain optional retained message from MqttPublishPayload()
2021-01-01 16:04:36 +00:00
# ifdef ESP32
// this takes the mutex, and will be release when the class is destroyed -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
2021-09-06 18:52:20 +01:00
TasAutoMutex mutex ( ( SemaphoreHandle_t * ) & TasmotaGlobal . log_buffer_mutex ) ;
2021-01-01 16:04:36 +00:00
# endif // ESP32
2020-12-31 11:36:35 +00:00
2021-10-07 14:39:04 +01:00
char mxtime [ 21 ] ; // "13:45:21.999-123/12 "
2021-10-04 16:12:42 +01:00
snprintf_P ( mxtime , sizeof ( mxtime ) , PSTR ( " %02d " D_HOUR_MINUTE_SEPARATOR " %02d " D_MINUTE_SECOND_SEPARATOR " %02d.%03d " ) ,
RtcTime . hour , RtcTime . minute , RtcTime . second , RtcMillis ( ) ) ;
if ( Settings - > flag5 . show_heap_with_timestamp ) {
2021-10-07 14:39:04 +01:00
# ifdef ESP8266
2021-10-04 16:12:42 +01:00
snprintf_P ( mxtime , sizeof ( mxtime ) , PSTR ( " %s-%03d " ) ,
mxtime , ESP_getFreeHeap1024 ( ) ) ;
2021-10-07 14:39:04 +01:00
# else
snprintf_P ( mxtime , sizeof ( mxtime ) , PSTR ( " %s-%03d/%02d " ) ,
2021-10-08 10:52:50 +01:00
mxtime , ESP_getFreeHeap1024 ( ) , ESP_getHeapFragmentation ( ) ) ;
2021-10-07 14:39:04 +01:00
# endif
2021-10-04 16:12:42 +01:00
}
strcat ( mxtime , " " ) ;
2017-01-28 13:41:01 +00:00
2021-06-10 17:06:11 +01:00
char empty [ 2 ] = { 0 } ;
if ( ! log_data_payload ) { log_data_payload = empty ; }
if ( ! log_data_retained ) { log_data_retained = empty ; }
2020-10-30 11:29:48 +00:00
if ( ( loglevel < = TasmotaGlobal . seriallog_level ) & &
( TasmotaGlobal . masterlog_level < = TasmotaGlobal . seriallog_level ) ) {
2022-06-07 13:11:23 +01:00
TasConsole . printf ( " %s%s%s%s \r \n " , mxtime , log_data , log_data_payload , log_data_retained ) ;
2022-06-06 16:48:40 +01:00
# ifdef USE_SERIAL_BRIDGE
2022-06-07 13:11:23 +01:00
SerialBridgePrintf ( " %s%s%s%s \r \n " , mxtime , log_data , log_data_payload , log_data_retained ) ;
2022-06-06 16:48:40 +01:00
# endif // USE_SERIAL_BRIDGE
2017-09-14 13:20:27 +01:00
}
2020-12-18 14:37:20 +00:00
2021-06-11 15:02:49 +01:00
if ( ! TasmotaGlobal . log_buffer ) { return ; } // Leave now if there is no buffer available
2021-06-11 17:14:12 +01:00
uint32_t highest_loglevel = Settings - > weblog_level ;
if ( Settings - > mqttlog_level > highest_loglevel ) { highest_loglevel = Settings - > mqttlog_level ; }
2020-12-18 14:37:20 +00:00
if ( TasmotaGlobal . syslog_level > highest_loglevel ) { highest_loglevel = TasmotaGlobal . syslog_level ; }
2020-12-22 14:26:07 +00:00
if ( TasmotaGlobal . templog_level > highest_loglevel ) { highest_loglevel = TasmotaGlobal . templog_level ; }
2020-12-24 10:42:59 +00:00
if ( TasmotaGlobal . uptime < 3 ) { highest_loglevel = LOG_LEVEL_DEBUG_MORE ; } // Log all before setup correct log level
2020-12-22 14:26:07 +00:00
2020-12-18 15:30:37 +00:00
if ( ( loglevel < = highest_loglevel ) & & // Log only when needed
2020-12-18 14:37:20 +00:00
( TasmotaGlobal . masterlog_level < = highest_loglevel ) ) {
2018-01-30 13:14:55 +00:00
// Delimited, zero-terminated buffer of log lines.
2020-12-18 14:37:20 +00:00
// Each entry has this format: [index][loglevel][log data]['\1']
2021-06-09 09:14:39 +01:00
2021-06-10 17:06:11 +01:00
// Truncate log messages longer than MAX_LOGSZ which is the log buffer size minus 64 spare
uint32_t log_data_len = strlen ( log_data ) + strlen ( log_data_payload ) + strlen ( log_data_retained ) ;
char too_long [ TOPSZ ] ;
2021-06-09 16:24:28 +01:00
if ( log_data_len > MAX_LOGSZ ) {
2021-06-10 17:06:11 +01:00
snprintf_P ( too_long , sizeof ( too_long ) - 20 , PSTR ( " %s%s " ) , log_data , log_data_payload ) ; // 20 = strlen("... 123456 truncated")
snprintf_P ( too_long , sizeof ( too_long ) , PSTR ( " %s... %d truncated " ) , too_long , log_data_len ) ;
log_data = too_long ;
log_data_payload = empty ;
log_data_retained = empty ;
2021-06-09 09:14:39 +01:00
}
2020-12-18 15:30:37 +00:00
TasmotaGlobal . log_buffer_pointer & = 0xFF ;
if ( ! TasmotaGlobal . log_buffer_pointer ) {
TasmotaGlobal . log_buffer_pointer + + ; // Index 0 is not allowed as it is the end of char string
2020-10-28 16:32:07 +00:00
}
2020-12-18 15:30:37 +00:00
while ( TasmotaGlobal . log_buffer_pointer = = TasmotaGlobal . log_buffer [ 0 ] | | // If log already holds the next index, remove it
2021-06-10 17:06:11 +01:00
strlen ( TasmotaGlobal . log_buffer ) + strlen ( log_data ) + strlen ( log_data_payload ) + strlen ( log_data_retained ) + strlen ( mxtime ) + 4 > LOG_BUFFER_SIZE ) // 4 = log_buffer_pointer + '\1' + '\0'
2018-01-30 13:14:55 +00:00
{
2020-12-18 15:30:37 +00:00
char * it = TasmotaGlobal . log_buffer ;
it + + ; // Skip log_buffer_pointer
it + = strchrspn ( it , ' \1 ' ) ; // Skip log line
it + + ; // Skip delimiting "\1"
memmove ( TasmotaGlobal . log_buffer , it , LOG_BUFFER_SIZE - ( it - TasmotaGlobal . log_buffer ) ) ; // Move buffer forward to remove oldest log line
2017-04-25 17:24:42 +01:00
}
2021-06-11 15:02:49 +01:00
snprintf_P ( TasmotaGlobal . log_buffer , LOG_BUFFER_SIZE , PSTR ( " %s%c%c%s%s%s%s \1 " ) ,
2021-06-10 17:06:11 +01:00
TasmotaGlobal . log_buffer , TasmotaGlobal . log_buffer_pointer + + , ' 0 ' + loglevel , mxtime , log_data , log_data_payload , log_data_retained ) ;
2020-12-18 15:30:37 +00:00
TasmotaGlobal . log_buffer_pointer & = 0xFF ;
if ( ! TasmotaGlobal . log_buffer_pointer ) {
TasmotaGlobal . log_buffer_pointer + + ; // Index 0 is not allowed as it is the end of char string
2020-10-28 16:32:07 +00:00
}
2017-01-28 13:41:01 +00:00
}
}
2020-01-14 11:47:48 +00:00
2021-01-23 12:21:17 +00:00
void AddLog ( uint32_t loglevel , PGM_P formatP , . . . ) {
2021-10-07 14:39:04 +01:00
uint32_t highest_loglevel = TasmotaGlobal . seriallog_level ;
if ( Settings - > weblog_level > highest_loglevel ) { highest_loglevel = Settings - > weblog_level ; }
if ( Settings - > mqttlog_level > highest_loglevel ) { highest_loglevel = Settings - > mqttlog_level ; }
if ( TasmotaGlobal . syslog_level > highest_loglevel ) { highest_loglevel = TasmotaGlobal . syslog_level ; }
if ( TasmotaGlobal . templog_level > highest_loglevel ) { highest_loglevel = TasmotaGlobal . templog_level ; }
if ( TasmotaGlobal . uptime < 3 ) { highest_loglevel = LOG_LEVEL_DEBUG_MORE ; } // Log all before setup correct log level
2021-01-23 12:21:17 +00:00
2021-10-07 14:39:04 +01:00
// If no logging is requested then do not access heap to fight fragmentation
if ( ( loglevel < = highest_loglevel ) & & ( TasmotaGlobal . masterlog_level < = highest_loglevel ) ) {
va_list arg ;
va_start ( arg , formatP ) ;
char * log_data = ext_vsnprintf_malloc_P ( formatP , arg ) ;
va_end ( arg ) ;
if ( log_data = = nullptr ) { return ; }
AddLogData ( loglevel , log_data ) ;
free ( log_data ) ;
}
2019-08-08 14:17:39 +01:00
}
2022-11-11 13:34:58 +00:00
void AddLogBuffer ( uint32_t loglevel , uint8_t * buffer , uint32_t count ) {
2019-08-22 11:36:13 +01:00
char hex_char [ ( count * 3 ) + 2 ] ;
2021-06-05 10:47:09 +01:00
AddLog ( loglevel , PSTR ( " DMP: %s " ) , ToHex_P ( buffer , count , hex_char , sizeof ( hex_char ) , ' ' ) ) ;
2018-02-04 17:09:09 +00:00
}
2022-11-11 13:34:58 +00:00
void AddLogSerial ( ) {
AddLogBuffer ( LOG_LEVEL_DEBUG , ( uint8_t * ) TasmotaGlobal . serial_in_buffer , TasmotaGlobal . serial_in_byte_counter ) ;
2018-02-13 13:30:30 +00:00
}
2020-04-12 10:26:33 +01:00
void AddLogMissed ( const char * sensor , uint32_t misses )
2018-07-10 21:12:16 +01:00
{
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( " SNS: %s missed %d " ) , sensor , SENSOR_MAX_MISS - misses ) ;
2018-07-10 21:12:16 +01:00
}
2020-04-29 16:44:03 +01:00
2020-12-31 15:17:30 +00:00
void AddLogSpi ( bool hardware , uint32_t clk , uint32_t mosi , uint32_t miso ) {
// Needs optimization
uint32_t enabled = ( hardware ) ? TasmotaGlobal . spi_enabled : TasmotaGlobal . soft_spi_enabled ;
switch ( enabled ) {
case SPI_MOSI :
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " SPI: %s using GPIO%02d(CLK) and GPIO%02d(MOSI) " ) ,
2020-12-31 15:17:30 +00:00
( hardware ) ? PSTR ( " Hardware " ) : PSTR ( " Software " ) , clk , mosi ) ;
break ;
case SPI_MISO :
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " SPI: %s using GPIO%02d(CLK) and GPIO%02d(MISO) " ) ,
2020-12-31 15:17:30 +00:00
( hardware ) ? PSTR ( " Hardware " ) : PSTR ( " Software " ) , clk , miso ) ;
break ;
case SPI_MOSI_MISO :
2021-01-23 15:26:23 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( " SPI: %s using GPIO%02d(CLK), GPIO%02d(MOSI) and GPIO%02d(MISO) " ) ,
2020-12-31 15:17:30 +00:00
( hardware ) ? PSTR ( " Hardware " ) : PSTR ( " Software " ) , clk , mosi , miso ) ;
break ;
}
}
2022-06-27 18:30:04 +01:00
/*********************************************************************************************\
* HTML and URL encode
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const char kUnescapeCode [ ] = " &>< \" \' \\ " ;
const char kEscapeCode [ ] PROGMEM = " &|>|<|"|'|\ " ;
String HtmlEscape ( const String unescaped ) {
char escaped [ 10 ] ;
size_t ulen = unescaped . length ( ) ;
String result ;
result . reserve ( ulen ) ; // pre-reserve the required space to avoid mutiple reallocations
for ( size_t i = 0 ; i < ulen ; i + + ) {
char c = unescaped [ i ] ;
char * p = strchr ( kUnescapeCode , c ) ;
if ( p ! = nullptr ) {
result + = GetTextIndexed ( escaped , sizeof ( escaped ) , p - kUnescapeCode , kEscapeCode ) ;
} else {
result + = c ;
}
}
return result ;
}
String UrlEscape ( const char * unescaped ) {
static const char * hex = " 0123456789ABCDEF " ;
String result ;
result . reserve ( strlen ( unescaped ) ) ;
while ( * unescaped ! = ' \0 ' ) {
if ( ( ' a ' < = * unescaped & & * unescaped < = ' z ' ) | |
( ' A ' < = * unescaped & & * unescaped < = ' Z ' ) | |
( ' 0 ' < = * unescaped & & * unescaped < = ' 9 ' ) | |
* unescaped = = ' - ' | | * unescaped = = ' _ ' | | * unescaped = = ' . ' | | * unescaped = = ' ~ ' )
{
result + = * unescaped ;
}
else
{
result + = ' % ' ;
result + = hex [ * unescaped > > 4 ] ;
result + = hex [ * unescaped & 0xf ] ;
}
unescaped + + ;
}
return result ;
}
2020-05-22 21:14:17 +01:00
/*********************************************************************************************\
* Uncompress static PROGMEM strings
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2020-06-06 19:04:10 +01:00
# ifdef USE_UNISHOX_COMPRESSION
2020-05-22 21:14:17 +01:00
2020-05-22 22:05:55 +01:00
# include <unishox.h>
2020-05-22 21:14:17 +01:00
Unishox compressor ;
2020-12-21 08:56:04 +00:00
// New variant where you provide the String object yourself
int32_t DecompressNoAlloc ( const char * compressed , size_t uncompressed_size , String & content ) {
2020-05-22 21:14:17 +01:00
uncompressed_size + = 2 ; // take a security margin
// We use a nasty trick here. To avoid allocating twice the buffer,
// we first extend the buffer of the String object to the target size (maybe overshooting by 7 bytes)
// then we decompress in this buffer,
// and finally assign the raw string to the String, which happens to work: String uses memmove(), so overlapping works
content . reserve ( uncompressed_size ) ;
char * buffer = content . begin ( ) ;
int32_t len = compressor . unishox_decompress ( compressed , strlen_P ( compressed ) , buffer , uncompressed_size ) ;
if ( len > 0 ) {
buffer [ len ] = 0 ; // terminate string with NULL
content = buffer ; // copy in place
}
2020-12-21 08:56:04 +00:00
return len ;
}
String Decompress ( const char * compressed , size_t uncompressed_size ) {
String content ( " " ) ;
DecompressNoAlloc ( compressed , uncompressed_size , content ) ;
2020-05-22 21:14:17 +01:00
return content ;
}
2020-08-20 15:22:40 +01:00
# endif // USE_UNISHOX_COMPRESSION