Tasmota/tasmota/tasmota_support/support.ino

2619 lines
80 KiB
Arduino
Raw Normal View History

2017-01-28 13:41:01 +00:00
/*
support.ino - support for Tasmota
2021-01-01 12:44:04 +00:00
Copyright (C) 2021 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <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;
}
/*********************************************************************************************\
* Watchdog extension (https://github.com/esp8266/Arduino/issues/1532)
\*********************************************************************************************/
#ifdef ESP8266
2018-11-20 14:00:24 +00:00
#include <Ticker.h>
Ticker tickerOSWatch;
const uint32_t OSWATCH_RESET_TIME = 120;
static unsigned long oswatch_last_loop_time;
uint8_t oswatch_blocked_loop = 0;
#ifndef USE_WS2812_DMA // Collides with Neopixelbus but solves exception
//void OsWatchTicker() IRAM_ATTR;
#endif // USE_WS2812_DMA
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;
#ifdef DEBUG_THEO
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);
#endif // DEBUG_THEO
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
RtcSettings.oswatch_blocked_loop = 1;
RtcSettingsSave();
2019-12-28 13:54:26 +00:00
// ESP.restart(); // normal reboot
2019-12-28 13:54:26 +00:00
// ESP.reset(); // hard reset
// Force an exception to get a stackdump
// 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
}
}
void OsWatchInit(void) {
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);
}
void OsWatchLoop(void) {
oswatch_last_loop_time = millis();
// while(1) delay(1000); // this will trigger the os watch
}
bool OsWatchBlockedLoop(void) {
2019-11-20 20:49:57 +00:00
return oswatch_blocked_loop;
}
#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 wont change
REASON_SOFT_WDT_RST = 3, // "Software Watchdog" software watch dog reset, GPIO status wont change
REASON_SOFT_RESTART = 4, // "Software/System restart" software restart ,system_restart , GPIO status wont 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
}
bool ResetReasonPowerOn(void) {
uint32_t reset_reason = ESP_ResetInfoReason();
return ((reset_reason == REASON_DEFAULT_RST) || (reset_reason == REASON_EXT_SYS_RST));
}
String GetResetReason(void) {
if (OsWatchBlockedLoop()) {
2019-10-30 13:08:43 +00:00
char buff[32];
strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff));
return String(buff);
} else {
return ESP_getResetReason();
}
}
2021-01-01 16:04:36 +00:00
#ifdef ESP32
/*********************************************************************************************\
* ESP32 AutoMutex
\*********************************************************************************************/
//////////////////////////////////////////
// automutex.
// create a mute in your driver with:
// void *mutex = nullptr;
//
// then protect any function with
// TasAutoMutex m(&mutex, "somename");
// - mutex is automatically initialised if not already intialised.
// - 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.
// - 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)
class TasAutoMutex {
SemaphoreHandle_t mutex;
bool taken;
int maxWait;
const char *name;
public:
TasAutoMutex(SemaphoreHandle_t* mutex, const char *name = "", int maxWait = 40, bool take=true);
~TasAutoMutex();
void give();
void take();
static void init(SemaphoreHandle_t* ptr);
};
//////////////////////////////////////////
TasAutoMutex::TasAutoMutex(SemaphoreHandle_t*mutex, const char *name, int maxWait, bool take) {
2021-01-01 16:04:36 +00:00
if (mutex) {
if (!(*mutex)){
2021-01-02 13:59:02 +00:00
TasAutoMutex::init(mutex);
}
this->mutex = *mutex;
this->maxWait = maxWait;
this->name = name;
2021-01-01 16:04:36 +00:00
if (take) {
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);
// }
}
} else {
2021-01-01 16:04:36 +00:00
this->mutex = (SemaphoreHandle_t)nullptr;
}
}
2021-01-01 16:04:36 +00:00
TasAutoMutex::~TasAutoMutex() {
if (this->mutex) {
if (this->taken) {
xSemaphoreGiveRecursive(this->mutex);
this->taken = false;
}
}
}
void TasAutoMutex::init(SemaphoreHandle_t* ptr) {
SemaphoreHandle_t mutex = xSemaphoreCreateRecursiveMutex();
(*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;
}
2021-01-01 16:04:36 +00:00
void TasAutoMutex::give() {
if (this->mutex) {
if (this->taken) {
xSemaphoreGiveRecursive(this->mutex);
this->taken= false;
}
}
}
2021-01-01 16:04:36 +00:00
void TasAutoMutex::take() {
if (this->mutex) {
if (!this->taken) {
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);
// }
}
}
}
2021-01-01 16:04:36 +00:00
#endif // ESP32
/*********************************************************************************************\
* Miscellaneous
\*********************************************************************************************/
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
// Get span until single character in string
size_t strchrspn(const char *str1, int character)
{
size_t ret = 0;
char *start = (char*)str1;
char *end = strchr(str1, character);
if (end) ret = end - start;
return ret;
}
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;
}
float CharToFloat(const char *str)
{
// simple ascii to double, because atof or strtod are too large
char strbuf[24];
strlcpy(strbuf, str, sizeof(strbuf));
char *pt = strbuf;
if (*pt == '\0') { return 0.0f; }
2021-08-16 10:11:46 +01:00
while ((*pt != '\0') && isspace(*pt)) { pt++; } // Trim leading spaces
signed char sign = 1;
if (*pt == '-') { sign = -1; }
if (*pt == '-' || *pt == '+') { pt++; } // Skip any sign
float left = 0;
if (*pt != '.') {
left = atoi(pt); // Get left part
while (isdigit(*pt)) { pt++; } // Skip number
}
float right = 0;
if (*pt == '.') {
pt++;
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
right = atoi(pt); // Decimal part
while (isdigit(*pt)) {
pt++;
right /= 10.0f;
}
}
float result = left + right;
if (sign < 0) {
return -result; // Add negative sign
}
return result;
}
int TextToInt(char *str)
{
char *p;
uint8_t radix = 10;
if ('#' == str[0]) {
radix = 16;
str++;
}
return strtol(str, &p, radix);
}
char* dtostrfd(double number, unsigned char prec, char *s)
{
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"));
return s;
} else {
return dtostrf(number, 1, prec, s);
}
}
char* Unescape(char* buffer, uint32_t* size)
{
uint8_t* read = (uint8_t*)buffer;
uint8_t* write = (uint8_t*)buffer;
int32_t start_size = *size;
int32_t end_size = *size;
uint8_t che = 0;
2022-11-11 10:47:11 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR("DBG: UnescapeIn %*_H"), *size, (uint8_t*)buffer);
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
case 's': che = ' '; break; // 20 Space
case 't': che = '\t'; break; // 09 Horizontal tab
case 'v': che = '\v'; break; // 0B Vertical tab
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
// case '?': che = '\?'; break; // 3F Question mark
default : {
che = chi;
*write++ = ch;
end_size++;
}
}
*write++ = che;
}
}
}
*size = end_size;
*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);
return buffer;
}
2020-10-10 16:00:37 +01:00
char* RemoveSpace(char* p) {
// Remove white-space character (' ','\t','\n','\v','\f','\r')
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;
}
}
*write++ = '\0';
2020-10-10 15:44:35 +01:00
return p;
}
char* ReplaceChar(char* p, char find, char replace) {
char* write = (char*)p;
char* read = (char*)p;
char ch = '.';
while (ch != '\0') {
ch = *read++;
if (ch == find) {
ch = replace;
}
*write++ = ch;
}
return p;
}
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;
}
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;
}
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) {
char case_source[strlen_P(source) +1];
UpperCase_P(case_source, source);
char case_search[strlen_P(search) +1];
UpperCase_P(case_search, search);
2021-05-19 15:39:56 +01:00
return (strstr(case_source, case_search) != nullptr);
}
bool IsNumeric(const char* value) {
2021-08-16 10:44:27 +01:00
// Test for characters '-.0123456789'
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
if (*p != '\0') {
2021-08-16 10:11:46 +01:00
while ((*p != '\0') && isspace(*p)) { p++; } // Trim leading spaces
char* q = p + strlen(p) -1;
2021-08-16 10:11:46 +01:00
while ((q >= p) && isspace(*q)) { q--; } // Trim trailing spaces
q++;
*q = '\0';
}
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;
}
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;
}
char* NoAlNumToUnderscore(char* dest, const char* source)
{
char* write = dest;
const char* read = source;
char ch = '.';
while (ch != '\0') {
ch = *read++;
*write++ = (isalnum(ch) || ('\0' == ch)) ? ch : '_';
}
return dest;
}
2019-11-20 19:53:12 +00:00
char IndexSeparator(void)
{
/*
// 20 bytes more costly !?!
const char separators[] = { "-_" };
2021-06-11 17:14:12 +01:00
return separators[Settings->flag3.use_underscore];
*/
2021-06-11 17:14:12 +01:00
if (Settings->flag3.use_underscore) { // SetOption64 - Enable "_" instead of "-" as sensor index separator
return '_';
} else {
return '-';
}
}
void SetShortcutDefault(void)
{
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';
}
}
2019-11-20 19:53:12 +00:00
uint8_t Shortcut(void)
{
uint8_t result = 10;
if ('\0' == XdrvMailbox.data[1]) { // Only allow single character input for shortcut
if (('"' == XdrvMailbox.data[0]) || ('0' == XdrvMailbox.data[0])) {
result = SC_CLEAR;
} else {
result = atoi(XdrvMailbox.data); // 1 = SC_DEFAULT, 2 = SC_USER
if (0 == result) {
result = 10;
}
}
}
return result;
}
bool ValidIpAddress(const char* str)
{
2021-01-18 15:32:58 +00:00
IPAddress ip_address;
return ip_address.fromString(str);
}
bool ParseIPv4(uint32_t* addr, const char* str_p)
{
uint8_t *part = (uint8_t*)addr;
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);
*addr = 0;
for (i = 0; i < 4; i++) {
part[i] = strtoul(str, nullptr, 10); // Convert byte
str = strchr(str, '.');
if (str == nullptr || *str == '\0') {
break; // No more separators, exit
}
str++; // Point to next character after separator
}
return (3 == i);
}
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.
uint32_t version = 0;
uint32_t i = 0;
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.
// Loop through the version string, splitting on '.' seperators.
for (char *str = strtok_r(version_dup, ".", &str_ptr); str && i < sizeof(VERSION); str = strtok_r(nullptr, ".", &str_ptr), i++) {
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++;
}
}
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).
// 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);
}
int32_t UpdateDevicesPresent(int32_t change) {
int32_t difference = 0;
int32_t devices_present = TasmotaGlobal.devices_present; // Between 0 and 32
devices_present += change;
if (devices_present < 0) { // Support down to 0
difference = devices_present;
devices_present = 0;
}
else if (devices_present >= POWER_SIZE) { // Support up to uint32_t as bitmask
difference = devices_present - POWER_SIZE;
devices_present = POWER_SIZE;
}
TasmotaGlobal.devices_present = devices_present;
return difference;
}
2019-07-28 12:54:52 +01:00
char* GetPowerDevice(char* dest, uint32_t idx, size_t size, uint32_t option)
{
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];
snprintf_P(sidx, sizeof(sidx), PSTR("%d"), idx); // x
strncat(dest, sidx, size - strlen(dest) -1); // POWERx
}
return dest;
}
2019-07-28 12:54:52 +01:00
char* GetPowerDevice(char* dest, uint32_t idx, size_t size)
{
return GetPowerDevice(dest, idx, size, 0);
}
float ConvertTempToFahrenheit(float tc) {
if (isnan(tc)) { return NAN; }
float result = tc;
if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
result = tc * 1.8f + 32; // Fahrenheit
}
result = result + (0.1f * Settings->temp_comp);
return result;
}
float ConvertTempToCelsius(float tf) {
if (isnan(tf)) { return NAN; }
float result = tf;
if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
result = (tf - 32) / 1.8f; // Celsius
}
return result;
}
void UpdateGlobalTemperature(float t) {
if (!Settings->global_sensor_index[0] && !TasmotaGlobal.user_globals[0]) {
TasmotaGlobal.global_update = TasmotaGlobal.uptime;
TasmotaGlobal.temperature_celsius = t;
}
}
float ConvertTemp(float t) {
UpdateGlobalTemperature(t);
return ConvertTempToFahrenheit(t);
}
char TempUnit(void) {
// 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];
}
float ConvertHumidity(float h) {
2020-03-16 17:29:55 +00:00
float result = h;
if (!Settings->global_sensor_index[1] && !TasmotaGlobal.user_globals[1]) {
TasmotaGlobal.global_update = TasmotaGlobal.uptime;
TasmotaGlobal.humidity = h;
}
result = result + (0.1f * Settings->hum_comp);
2020-03-16 17:29:55 +00:00
return result;
}
float CalcTempHumToDew(float t, float h) {
if (isnan(h) || isnan(t)) { return NAN; }
2021-06-11 17:14:12 +01:00
if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
t = (t - 32) / 1.8f; // Celsius
}
float gamma = TaylorLog(h / 100) + 17.62f * t / (243.5f + t);
float result = (243.5f * gamma / (17.62f - gamma));
2021-06-11 17:14:12 +01:00
if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
result = result * 1.8f + 32; // Fahrenheit
}
return result;
}
float CalcTempHumToAbsHum(float t, float h) {
if (isnan(t) || isnan(h)) { return NAN; }
// taken from https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
// precision is about 0.1°C in range -30 to 35°C
// August-Roche-Magnus 6.1094 exp(17.625 x T)/(T + 243.04)
// Buck (1981) 6.1121 exp(17.502 x T)/(T + 240.97)
// reference https://www.eas.ualberta.ca/jdwilson/EAS372_13/Vomel_CIRES_satvpformulae.html
if (Settings->flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit
t = (t - 32) / 1.8f; // Celsius
}
float temp = FastPrecisePow(2.718281828f, (17.67f * t) / (t + 243.5f));
const float mw = 18.01534f; // Molar mass of water g/mol
const float r = 8.31447215f; // Universal gas constant J/mol/K
// return (6.112 * temp * h * 2.1674) / (273.15 + t); // Simplified version
return (6.112f * temp * h * mw) / ((273.15f + t) * r); // Long version
}
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;
}
float ConvertPressure(float p) {
// Convert hPa to mmHg (or inHg)
2018-11-01 15:36:22 +00:00
float result = p;
if (!Settings->global_sensor_index[2] && !TasmotaGlobal.user_globals[2]) {
TasmotaGlobal.global_update = TasmotaGlobal.uptime;
TasmotaGlobal.pressure_hpa = 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 * 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;
}
float ConvertPressureForSeaLevel(float pressure) {
if (pressure == 0.0f) {
return pressure;
}
return ConvertPressure((pressure / FastPrecisePowf(1.0f - ((float)Settings->altitude / 44330.0f), 5.255f)) - 21.6f);
}
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));
}
float ConvertSpeed(float s)
{
// Entry in m/s
2021-06-11 17:14:12 +01:00
return s * kSpeedConversionFactor[Settings->flag2.speed_conversion];
}
String SpeedUnit(void) {
char text[8];
return String(GetTextIndexed(text, sizeof(text), Settings->flag2.speed_conversion, kSpeedUnit));
}
2018-11-14 13:32:09 +00:00
void ResetGlobalValues(void)
{
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;
}
}
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;
}
char* GetTextIndexed(char* destination, size_t destination_size, uint32_t index, const char* haystack)
{
// 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;
}
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) {
GetTextIndexed(XdrvMailbox.command, CMDSZ, 0, haystack); // Get prefix if available
int prefix_length = strlen(XdrvMailbox.command);
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
}
}
size_t syn_count = synonyms ? pgm_read_byte(synonyms) : 0;
int command_code = GetCommandCode(XdrvMailbox.command + prefix_length, CMDSZ, XdrvMailbox.topic + prefix_length, haystack);
if (command_code > 0) { // Skip prefix
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);
}
return true;
}
return false;
}
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
"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
"UP|" D_OPEN "|" // 100
"ALL" ; // 255
2022-12-10 22:54:09 +00:00
const uint8_t sNumbers[] PROGMEM = { 0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,
2,2,2,
3,3,
4,4,
2022-12-10 22:54:09 +00:00
100,100,
255 };
2021-08-13 15:35:24 +01:00
int GetStateNumber(const char *state_text)
{
char command[CMDSZ];
int state_number = GetCommandCode(command, sizeof(command), state_text, kOptions);
if (state_number >= 0) {
state_number = pgm_read_byte(sNumbers + state_number);
}
return state_number;
}
uint32_t GetHash(const char *buffer, size_t size)
{
uint32_t hash = 0;
for (uint32_t i = 0; i <= size; i++) {
hash += (uint8_t)*buffer++ * (i +1);
}
return hash;
}
void ShowSource(uint32_t source)
{
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));
}
}
2019-07-28 12:54:52 +01:00
void WebHexCode(uint32_t i, const char* code)
{
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
/*
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
}
*/
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);
}
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-07-28 12:54:52 +01:00
uint32_t WebColor(uint32_t i)
{
2021-06-11 17:14:12 +01:00
uint32_t j = sizeof(Settings->web_color) / 3; // First area contains j = 18 colors
/*
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];
*/
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);
}
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];
return tcolor;
}
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);
}
}
/*********************************************************************************************\
* Response data handling
\*********************************************************************************************/
const uint16_t TIMESZ = 100; // Max number of characters in time string
char* ResponseGetTime(uint32_t format, char* time_str)
{
switch (format) {
case 1:
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\",\"Epoch\":%u"), GetDateAndTime(DT_LOCAL).c_str(), UtcTime());
break;
case 2:
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":%u"), UtcTime());
break;
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());
break;
default:
snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str());
}
return time_str;
}
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) {
#ifdef MQTT_DATA_STRING
2021-06-09 16:24:28 +01:00
return MAX_LOGSZ; // Arbitratry max length satisfying full log entry
#else
2021-05-23 15:50:17 +01:00
return sizeof(TasmotaGlobal.mqtt_data);
#endif
2021-05-23 15:50:17 +01:00
}
2021-05-23 13:42:27 +01:00
uint32_t ResponseLength(void) {
#ifdef MQTT_DATA_STRING
return TasmotaGlobal.mqtt_data.length();
#else
2021-05-23 13:42:27 +01:00
return strlen(TasmotaGlobal.mqtt_data);
#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
#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)
#else
2020-10-30 11:29:48 +00:00
TasmotaGlobal.mqtt_data[0] = '\0';
#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 {
#ifdef MQTT_DATA_STRING
TasmotaGlobal.mqtt_data.setCharAt(0,'{');
#else
2021-05-23 13:42:27 +01:00
TasmotaGlobal.mqtt_data[0] = '{';
#endif
2021-05-23 13:42:27 +01:00
}
int Response_P(const char* format, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
#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
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);
va_end(args);
return len;
#endif
}
int ResponseTime_P(const char* format, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
#ifdef MQTT_DATA_STRING
char timestr[100];
2021-06-11 17:14:12 +01:00
TasmotaGlobal.mqtt_data = ResponseGetTime(Settings->flag2.time_format, timestr);
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
va_list args;
va_start(args, format);
2021-06-11 17:14:12 +01:00
ResponseGetTime(Settings->flag2.time_format, TasmotaGlobal.mqtt_data);
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);
va_end(args);
return len + mlen;
#endif
}
int ResponseAppend_P(const char* format, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
#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
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);
va_end(args);
return len + mlen;
#endif
}
int ResponseAppendTimeFormat(uint32_t format)
{
char time_str[TIMESZ];
return ResponseAppend_P(ResponseGetTime(format, time_str));
}
int ResponseAppendTime(void)
{
2021-06-11 17:14:12 +01:00
return ResponseAppendTimeFormat(Settings->flag2.time_format);
}
int ResponseAppendTHD(float f_temperature, float f_humidity)
{
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);
}
int ResponseJsonEnd(void)
{
return ResponseAppend_P(PSTR("}"));
}
int ResponseJsonEndEnd(void)
{
return ResponseAppend_P(PSTR("}}"));
}
2021-05-23 15:50:17 +01:00
bool ResponseContains_P(const char* needle) {
/*
#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);
#endif
*/
return (strstr_P(ResponseData(), needle) != nullptr);
}
/*********************************************************************************************\
* 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-25 17:34:14 +01:00
#endif // ESP8266
2020-09-25 17:15:31 +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
}
}
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
}
void DigitalWrite(uint32_t gpio_pin, uint32_t index, uint32_t state) {
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)) {
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);
}
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
}
uint32_t ModuleTemplate(uint32_t module) {
uint32_t i = 0;
for (i = 0; i < sizeof(kModuleNiceList); i++) {
if (module == pgm_read_byte(kModuleNiceList + i)) {
break;
}
}
if (i == sizeof(kModuleNiceList)) { i = 0; }
return i;
}
2019-07-28 12:54:52 +01:00
bool ValidTemplateModule(uint32_t index)
{
for (uint32_t i = 0; i < sizeof(kModuleNiceList); i++) {
if (index == pgm_read_byte(kModuleNiceList + i)) {
return true;
}
}
return false;
}
2019-07-28 12:54:52 +01:00
bool ValidModule(uint32_t index)
{
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)
{
if (USER_MODULE == index) {
return String(SettingsText(SET_TEMPLATE_NAME));
} else {
#ifdef ESP32
index = ModuleTemplate(index);
#endif
char name[TOPSZ];
2020-03-28 17:08:43 +00:00
return String(GetTextIndexed(name, sizeof(name), index, kModuleNames));
}
}
2019-11-20 19:53:12 +00:00
String ModuleName(void)
{
2021-06-11 17:14:12 +01:00
return AnyModuleName(Settings->module);
}
2020-08-23 08:08:47 +01:00
#ifdef ESP8266
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-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 };
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);
} else {
2020-09-25 17:15:31 +01:00
memcpy_P(&template8, &kModules8285[module_template - TMP_WEMOS], sizeof(template8));
}
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);
uint32_t index = 0;
2020-09-29 13:08:48 +01:00
uint32_t size = sizeof(mycfgio); // template16[module_template].gp
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;
break;
}
case 3: {
2020-09-29 13:08:48 +01:00
size = sizeof(mytmplt); // template16[module_template]
break;
}
}
2020-09-25 17:15:31 +01:00
memcpy(ptr, &template16[index], size);
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-23 08:08:47 +01:00
#endif // ESP8266
#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
void TemplateGpios(myio *gp)
{
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)];
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));
} 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
}
// 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);
// 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++) {
#if defined(ESP32) && CONFIG_IDF_TARGET_ESP32C3
dest[i] = src[i];
#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++;
#elif defined(CONFIG_IDF_TARGET_ESP32)
dest[Esp32TemplateToPhy[i]] = src[i];
#else // ESP8266
if (6 == i) { j = 9; }
if (8 == i) { j = 12; }
dest[j] = src[i];
j++;
#endif
}
// 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-11-20 19:53:12 +00:00
gpio_flag ModuleFlag(void)
{
gpio_flag flag;
2021-06-11 17:14:12 +01:00
if (USER_MODULE == Settings->module) {
flag = Settings->user_template.flag;
} 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
}
return flag;
}
2019-07-28 12:54:52 +01:00
void ModuleDefault(uint32_t module)
{
if (USER_MODULE == module) { module = WEMOS; } // Generic
2021-06-11 17:14:12 +01:00
Settings->user_template_base = module;
#ifdef ESP32
module = ModuleTemplate(module);
#endif
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-11-20 19:53:12 +00:00
void SetModuleType(void)
{
2021-06-11 17:14:12 +01:00
TasmotaGlobal.module_type = (USER_MODULE == Settings->module) ? Settings->user_template_base : Settings->module;
#ifdef ESP32
if (TasmotaGlobal.emulated_module_type) {
TasmotaGlobal.module_type = TasmotaGlobal.emulated_module_type;
}
#endif
}
bool FlashPin(uint32_t pin)
{
#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
#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
#elif defined(CONFIG_IDF_TARGET_ESP32)
return (pin >= 28) && (pin <= 31); // ESP21 skip 28-31
#else // ESP8266
return (((pin > 5) && (pin < 9)) || (11 == pin));
#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
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
return (33<=pin) && (37>=pin); // ESP32S3: GPIOs 33..37 are usually used for PSRAM
#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
return (9==pin)||(10==pin);
#endif
}
uint32_t ValidPin(uint32_t pin, uint32_t gpio, uint8_t isTuya = false) {
if (FlashPin(pin)) {
2019-12-02 09:31:33 +00:00
return GPIO_NONE; // Disable flash pins GPIO6, GPIO7, GPIO8 and GPIO11
}
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
#elif defined(CONFIG_IDF_TARGET_ESP32)
// ignore
2021-09-04 13:20:09 +01:00
#else // not ESP32C3 and not ESP32S2
if (((WEMOS == Settings->module) || isTuya) && !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
}
}
2021-09-04 13:20:09 +01:00
#endif
2019-12-02 09:31:33 +00:00
return gpio;
}
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
}
bool ValidSpiPinUsed(uint32_t gpio) {
// ESP8266: If SPI pin selected chk if it's not one of the three Hardware SPI pins (12..14)
bool result = false;
if (PinUsed(gpio)) {
int pin = Pin(gpio);
result = ((pin < 12) || (pin > 14));
}
return result;
}
bool JsonTemplate(char* dataBuf)
{
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}
// AddLog(LOG_LEVEL_DEBUG, PSTR("TPL: |%s|"), dataBuf);
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();
if (!root) { return false; }
// All parameters are optional allowing for partial changes
JsonParserToken val = root[PSTR(D_JSON_NAME)];
if (val) {
SettingsUpdateText(SET_TEMPLATE_NAME, val.getStr());
}
JsonParserArray arr = root[PSTR(D_JSON_GPIO)];
if (arr) {
#ifdef ESP8266
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++) {
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 {
#endif
2021-06-11 17:14:12 +01:00
for (uint32_t i = 0; i < nitems(Settings->user_template.gp.io); i++) {
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
}
}
2020-09-30 14:26:46 +01:00
#ifdef ESP8266
}
2020-09-30 14:26:46 +01:00
#endif
val = root[PSTR(D_JSON_BASE)];
if (val) {
uint32_t base = val.getUInt();
if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; }
2021-06-11 17:14:12 +01:00
Settings->user_template_base = base -1; // Default WEMOS
}
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
char* backup_data = XdrvMailbox.data;
XdrvMailbox.data = (char*)val.getStr(); // Backlog commands
ReplaceChar(XdrvMailbox.data, '|', ';'); // Support '|' as command separator for JSON backwards compatibility
uint32_t backup_data_len = XdrvMailbox.data_len;
XdrvMailbox.data_len = 1; // Any data
uint32_t backup_index = XdrvMailbox.index;
XdrvMailbox.index = 0; // Backlog0 - no delay
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);
return true;
}
2019-11-20 19:53:12 +00:00
void TemplateJson(void)
{
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);
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];
if (gpio == AGPIO(GPIO_USER)) {
gpio = AGPIO(GPIO_NONE) +1;
}
ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", gpio);
}
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);
}
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;
}
uint32_t ConvertSerialConfig(uint8_t serial_config) {
#ifdef ESP8266
return (uint32_t)pgm_read_byte(kTasmotaSerialConfig + serial_config);
#elif defined(ESP32)
return (uint32_t)pgm_read_dword(kTasmotaSerialConfig + serial_config);
#else
#error "platform not supported"
#endif
}
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
Serial.begin(TasmotaGlobal.baudrate, (SerialConfig)ConvertSerialConfig(Settings->serial_config));
2021-09-20 13:31:00 +01:00
SetSerialSwap();
#endif // ESP8266
#ifdef ESP32
2023-03-26 15:37:29 +01:00
#if ARDUINO_USB_MODE
2022-04-25 11:03:53 +01:00
// 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
Serial.begin(TasmotaGlobal.baudrate, ConvertSerialConfig(Settings->serial_config));
2023-03-26 15:37:29 +01:00
#endif // Not ARDUINO_USB_MODE
2021-09-20 13:31:00 +01:00
#endif // ESP32
}
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) {
#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;
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);
}
}
/*********************************************************************************************/
uint8_t Bcd2Dec(uint8_t n) {
return n - 6 * (n >> 4);
}
uint8_t Dec2Bcd(uint8_t n) {
return n + 6 * (n / 10);
}
/*********************************************************************************************/
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
}
}
/*********************************************************************************************\
* Sleep aware time scheduler functions borrowed from ESPEasy
\*********************************************************************************************/
inline int32_t TimeDifference(uint32_t prev, uint32_t next)
{
return ((int32_t) (next - prev));
}
int32_t TimePassedSince(uint32_t timestamp)
{
// 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());
}
bool TimeReached(uint32_t timer)
{
// 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)
{
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);
}
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);
}
2023-05-24 16:24:48 +01:00
void SystemBusyDelay(uint32_t busy) {
/*
TasmotaGlobal.busy_time = millis();
SetNextTimeInterval(TasmotaGlobal.busy_time, busy +1);
if (!TasmotaGlobal.busy_time) {
TasmotaGlobal.busy_time++;
}
*/
TasmotaGlobal.busy_time = busy;
}
2023-05-24 16:24:48 +01:00
void SystemBusyDelayExecute(void) {
if (TasmotaGlobal.busy_time) {
/*
// Calls to millis() interrupt RMT and defeats our goal
if (!TimeReached(TasmotaGlobal.busy_time)) {
delay(1);
}
*/
delay(TasmotaGlobal.busy_time);
TasmotaGlobal.busy_time = 0;
}
}
2017-01-28 13:41:01 +00:00
/*********************************************************************************************\
* Syslog
*
* Example:
2021-01-23 15:26:23 +00:00
* AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_LOG "Any value %d"), value);
*
2017-01-28 13:41:01 +00: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;
}
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)
{
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;
}
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;
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) {
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;
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;
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;
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
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-19 16:29:14 +00:00
}
}
2020-12-22 14:26:07 +00:00
bool NeedLogRefresh(uint32_t req_loglevel, uint32_t index) {
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 -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
TasAutoMutex mutex((SemaphoreHandle_t *)&TasmotaGlobal.log_buffer_mutex);
2021-01-01 16:04:36 +00:00
#endif // ESP32
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) {
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
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 -
// i.e. when the functon leaves You CAN call mutex.give() to leave early.
TasAutoMutex mutex((SemaphoreHandle_t *)&TasmotaGlobal.log_buffer_mutex);
2021-01-01 16:04:36 +00:00
#endif // ESP32
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;
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
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;
}
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) {
// Ignore any logging when maxlog_level = 0 OR logging for levels equal or lower than maxlog_level
if (!TasmotaGlobal.maxlog_level || (loglevel > TasmotaGlobal.maxlog_level)) { 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.
TasAutoMutex mutex((SemaphoreHandle_t *)&TasmotaGlobal.log_buffer_mutex);
2021-01-01 16:04:36 +00:00
#endif // ESP32
char mxtime[21]; // "13:45:21.999-123/12 "
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) {
#ifdef ESP8266
snprintf_P(mxtime, sizeof(mxtime), PSTR("%s-%03d"),
mxtime, ESP_getFreeHeap1024());
#else
snprintf_P(mxtime, sizeof(mxtime), PSTR("%s-%03d/%02d"),
2021-10-08 10:52:50 +01:00
mxtime, ESP_getFreeHeap1024(), ESP_getHeapFragmentation());
#endif
}
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)) {
TasConsole.printf("%s%s%s%s\r\n", mxtime, log_data, log_data_payload, log_data_retained);
#ifdef USE_SERIAL_BRIDGE
SerialBridgePrintf("%s%s%s%s\r\n", mxtime, log_data, log_data_payload, log_data_retained);
#endif // USE_SERIAL_BRIDGE
}
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; }
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
(TasmotaGlobal.masterlog_level <= highest_loglevel)) {
// Delimited, zero-terminated buffer of log lines.
// Each entry has this format: [index][loglevel][log data]['\1']
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;
}
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'
{
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
}
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
}
}
void AddLog(uint32_t loglevel, PGM_P formatP, ...) {
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
// 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);
}
}
2022-11-11 13:34:58 +00:00
void AddLogBuffer(uint32_t loglevel, uint8_t *buffer, uint32_t count) {
char hex_char[(count * 3) + 2];
AddLog(loglevel, PSTR("DMP: %s"), ToHex_P(buffer, count, hex_char, sizeof(hex_char), ' '));
}
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);
}
void AddLogMissed(const char *sensor, uint32_t misses)
{
2021-01-23 15:26:23 +00:00
AddLog(LOG_LEVEL_DEBUG, PSTR("SNS: %s missed %d"), sensor, SENSOR_MAX_MISS - misses);
}
2020-04-29 16:44:03 +01: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)"),
(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)"),
(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)"),
(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 = "&amp;|&gt;|&lt;|&quot;|&apos;|&#92;";
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
\*********************************************************************************************/
#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