From cf52f1c99e1221a8944ae096d79c579a228f8318 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Sun, 24 Jan 2021 16:35:36 +0100 Subject: [PATCH] Added ext_snprintf to support extended types --- lib/default/Ext-printf/library.properties | 7 + lib/default/Ext-printf/src/ext_printf.cpp | 298 ++++++++++++++++++ lib/default/Ext-printf/src/ext_printf.h | 32 ++ .../Ext-printf/test/test_ext_printf.cpp | 106 +++++++ tasmota/support.ino | 73 ++--- tasmota/tasmota.ino | 1 + tasmota/xdrv_01_webserver.ino | 15 +- tasmota/xdrv_05_irremote.ino | 46 ++- tasmota/xdrv_05_irremote_full.ino | 52 +-- tasmota/xdrv_23_zigbee_1z_libs.ino | 4 +- tasmota/xdrv_23_zigbee_8_parsers.ino | 48 +-- tasmota/xdrv_23_zigbee_A_impl.ino | 17 +- 12 files changed, 553 insertions(+), 146 deletions(-) create mode 100644 lib/default/Ext-printf/library.properties create mode 100644 lib/default/Ext-printf/src/ext_printf.cpp create mode 100644 lib/default/Ext-printf/src/ext_printf.h create mode 100644 lib/default/Ext-printf/test/test_ext_printf.cpp diff --git a/lib/default/Ext-printf/library.properties b/lib/default/Ext-printf/library.properties new file mode 100644 index 000000000..bdb00e650 --- /dev/null +++ b/lib/default/Ext-printf/library.properties @@ -0,0 +1,7 @@ +name=Ext-printf +version=1.0 +author=Stephan Hadinger +maintainer=Stephan +sentence=Extension of snprintf() and vsnprintf() +paragraph=This library provides extended types support for snprintf (float, uint64_t) +architectures=esp8266, esp32 diff --git a/lib/default/Ext-printf/src/ext_printf.cpp b/lib/default/Ext-printf/src/ext_printf.cpp new file mode 100644 index 000000000..908aae11a --- /dev/null +++ b/lib/default/Ext-printf/src/ext_printf.cpp @@ -0,0 +1,298 @@ +/* + ext_printf.ino - Extended printf for Arduino objects + + Copyright (C) 2021 Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "ext_printf.h" +#include +#include + +/*********************************************************************************************\ + * va_list extended support + * + * va_list allows to get the next argument but not to get the address of this argument in the stack. + * + * We add `va_cur_ptr(va, TYPE)` to get a pointer to the current argument. + * This will allow to modify it in place and call back printf with altered arguments +\*********************************************************************************************/ + +// This code is heavily inspired by the gcc implementation of va_list +// https://github.com/gcc-mirror/gcc/blob/master/gcc/config/xtensa/xtensa.c + +// Here is the va_list structure: +// struct va_list { +// void * __va_stk; // offset 0 - pointer to arguments on the stack +// void * __va_reg; // offset 4 - pointer to arguments from registers +// uint32_t __va_ndx; // offset 8 - index in bytes of the argument (overshoot by sizeof(T)) +// } +// +// When `va_start()` is called, the first 6 arguments are passed through registers r2-r7 and +// are saved on the stack like local variables + +// The algorightm used by `va_arg()` is the following: +// /* Implement `va_arg'.  */ +// /* First align __va_ndx if necessary for this arg: +//     orig_ndx = (AP).__va_ndx; +//     if (__alignof__ (TYPE) > 4 ) +//       orig_ndx = ((orig_ndx + __alignof__ (TYPE) - 1) +// & -__alignof__ (TYPE)); */ +// /* Increment __va_ndx to point past the argument: +//     (AP).__va_ndx = orig_ndx + __va_size (TYPE); */ +// /* Check if the argument is in registers: +//     if ((AP).__va_ndx <= __MAX_ARGS_IN_REGISTERS * 4 +//         && !must_pass_in_stack (type)) +//       __array = (AP).__va_reg; */ +// /* ...otherwise, the argument is on the stack (never split between +//     registers and the stack -- change __va_ndx if necessary): +//     else +//       { +// if (orig_ndx <= __MAX_ARGS_IN_REGISTERS * 4) +//     (AP).__va_ndx = 32 + __va_size (TYPE); +// __array = (AP).__va_stk; +//       } */ +// /* Given the base array pointer (__array) and index to the subsequent +//     argument (__va_ndx), find the address: +//     __array + (AP).__va_ndx - (BYTES_BIG_ENDIAN && sizeof (TYPE) < 4 +// ? sizeof (TYPE) +// : __va_size (TYPE)) +//     The results are endian-dependent because values smaller than one word +//     are aligned differently.  */ + +// So we can simply get the argument address +#define MAX_ARGS_IN_REGISTERS 6 // ESP8266 passes 6 arguments by register, then on stack + +// #define va_cur_ptr(va,T) ( (T*) __va_cur_ptr(va,sizeof(T)) ) // we only support 4 bytes aligned arguments, so we don't need this one + +// void * __va_cur_ptr(va_list &va, size_t size) { +// size = (size + 3) & 0xFFFFFFFC; // round to upper 4 bytes boundary + +// uintptr_t * va_stk = (uintptr_t*) &va; +// uintptr_t * va_reg = 1 + (uintptr_t*) &va; +// uintptr_t * va_ndx = 2 + (uintptr_t*) &va; +// uintptr_t arr; + +// if (*va_ndx <= MAX_ARGS_IN_REGISTERS * 4) { +// arr = *va_reg; +// } else { +// arr = *va_stk; +// } +// return (void*) (arr + *va_ndx - size); +// } + +// reduced version when arguments are always 4 bytes +#define va_cur_ptr4(va,T) ( (T*) __va_cur_ptr4(va) ) +void * __va_cur_ptr4(va_list &va) { + uintptr_t * va_stk = (uintptr_t*) &va; + uintptr_t * va_reg = 1 + (uintptr_t*) &va; + uintptr_t * va_ndx = 2 + (uintptr_t*) &va; + uintptr_t arr; + + if (*va_ndx <= MAX_ARGS_IN_REGISTERS * 4) { + arr = *va_reg; + } else { + arr = *va_stk; + } + return (void*) (arr + *va_ndx - 4); +} + +// Example of logs with 8 arguments (+1 static argument) +// We see that the first 5 are from low in the stack (local variables) +// while the last 8 are upper in the stack pushed by caller +// +// Note 64 bits arguments cannot be split between registers and stack +// +// >>> Reading a_ptr=0x3FFFFD44 *a_ptr=1 +// >>> Reading a_ptr=0x3FFFFD48 *a_ptr=2 +// >>> Reading a_ptr=0x3FFFFD4C *a_ptr=3 +// >>> Reading a_ptr=0x3FFFFD50 *a_ptr=4 +// >>> Reading a_ptr=0x3FFFFD54 *a_ptr=5 +// >>> Reading a_ptr=0x3FFFFD70 *a_ptr=6 +// >>> Reading a_ptr=0x3FFFFD74 *a_ptr=7 +// >>> Reading a_ptr=0x3FFFFD78 *a_ptr=8 + +/*********************************************************************************************\ + * Genral function to convert u64 to hex +\*********************************************************************************************/ +// Simple function to print a 64 bits unsigned int +char * U64toHex(uint64_t value, char *str) { + // str must be at least 17 bytes long + str[16] = 0; // end of string + for (uint32_t i=0; i<16; i++) { // 16 digits + uint32_t n = value & 0x0F; + str[15 - i] = (n < 10) ? (char)n+'0' : (char)n-10+'A'; + value = value >> 4; + } + return str; +} + +/*********************************************************************************************\ + * snprintf extended + * +\*********************************************************************************************/ + +// get a fresh malloc allocated string based on the current pointer (can be in PROGMEM) +// It is the caller's responsibility to free the memory +char * copyStr(const char * str) { + if (str == nullptr) { return nullptr; } + char * cpy = (char*) malloc(strlen_P(str) + 1); + strcpy_P(cpy, str); + return cpy; +} + +int32_t ext_vsnprintf_P(char * buf, size_t buf_len, const char * fmt_P, va_list va) { + va_list va_cpy; + va_copy(va_cpy, va); + +#if defined(ESP8266) || defined(ESP32) // this works only for xtensa, other platforms needs va_list to be adapted + // iterate on fmt to extract arguments and patch them in place + char * fmt_cpy = copyStr(fmt_P); + if (fmt_cpy == nullptr) { return 0; } + char * fmt = fmt_cpy; + + const uint32_t ALLOC_SIZE = 12; + static char * allocs[ALLOC_SIZE] = {}; // initialized to zeroes + uint32_t alloc_idx = 0; + int32_t decimals = -2; // default to 2 decimals and remove trailing zeros + static char hex[20]; // buffer used for 64 bits, favor RAM instead of stack to remove pressure + + for (; *fmt != 0; ++fmt) { + if (alloc_idx >= ALLOC_SIZE) { break; } // buffer is full, don't continue parsing + if (*fmt == '%') { + fmt++; + char * fmt_start = fmt; + if (*fmt == '\0') { break; } // end of string + if (*fmt == '%') { continue; } // actual '%' char + if (*fmt == '*') { + va_arg(va, int32_t); // skip width argument as int + fmt++; + } + if (*fmt < 'A') { + decimals = strtol(fmt, nullptr, 10); + } + while (*fmt < 'A') { // brutal way to munch anything that is not a letter or '-' (or anything else) + // while ((*fmt >= '0' && *fmt <= '9') || (*fmt == '.') || (*fmt == '*') || (*fmt == '-' || (*fmt == ' ' || (*fmt == '+') || (*fmt == '#')))) { + fmt++; + } + + if (*fmt == '_') { // extension + for (; fmt_start <= fmt; fmt_start++) { + *fmt_start = '0'; + } + // *fmt = '0'; + fmt++; + uint32_t cur_val = va_arg(va, uint32_t); // current value + const char ** cur_val_ptr = va_cur_ptr4(va, const char*); // pointer to value on stack + char * new_val_str = (char*) ""; + switch (*fmt) { + // case 'D': + // decimals = *(int32_t*)cur_val_ptr; + // break; + + // `%_I` ouputs an IPv4 32 bits address passed as u32 into a decimal dotted format + case 'I': // Input is `uint32_t` 32 bits IP address, output is decimal dotted address + new_val_str = copyStr(IPAddress(cur_val).toString().c_str()); + allocs[alloc_idx++] = new_val_str; + break; + + // `%_f` or `%*_f` outputs a float with optionan number of decimals passed as first argument if `*` is present + // positive number of decimals means an exact number of decimals, can be `0` terminate + // negative number of decimals will suppress + // Ex: + // char c[128]; + // float f = 3.141f; + // ext_vsnprintf_P(c; szeof(c), "%_f %*_f %*_f", &f, 4, 1f, -4, %f); + // --> c will be "3.14 3.1410 3.141" + // Note: float MUST be passed by address, because C alsays promoted float to double when in vararg + case 'f': // input is `float`, printed to float with 2 decimals + { + bool truncate = false; + if (decimals < 0) { + decimals = -decimals; + truncate = true; + } + dtostrf(*(float*)cur_val, (decimals + 2), decimals, hex); + + if (truncate) { + uint32_t last = strlen(hex) - 1; + // remove trailing zeros + while (hex[last] == '0') { + hex[last--] = 0; // remove last char + } + // remove trailing dot + if (hex[last] == '.') { + hex[last] = 0; + } + } + new_val_str = copyStr(hex); + allocs[alloc_idx++] = new_val_str; + } + break; + // '%_X' outputs a 64 bits unsigned int to uppercase HEX with 16 digits + case 'X': // input is `uint64_t*`, printed as 16 hex digits (no prefix 0x) + { + U64toHex(*(uint64_t*)cur_val, hex); + new_val_str = copyStr(hex); + allocs[alloc_idx++] = new_val_str; + } + break; + // Trying to do String allocation alternatives, but not as interesting as I thought in the beginning + // case 's': + // { + // new_val_str = copyStr(((String*)cur_val)->c_str()); + // allocs[alloc_idx++] = new_val_str; + // } + // break; + // case 'S': + // { + // funcString_t * func_str = (funcString_t*) cur_val; + // new_val_str = copyStr((*func_str)().c_str()); + // allocs[alloc_idx++] = new_val_str; + // } + // break; + } + *cur_val_ptr = new_val_str; + *fmt = 's'; // replace `%_X` with `%0s` to display a string instead + + } else { + va_arg(va, int32_t); // munch one 32 bits argument and leave it unchanged + // we take the hypothesis here that passing 64 bits arguments is always unsupported in ESP8266 + } + fmt++; + } + } +#else // defined(ESP8266) || defined(ESP32) + #error "ext_printf is not suppoerted on this platform" +#endif // defined(ESP8266) || defined(ESP32) + int32_t ret = vsnprintf_P(buf, buf_len, fmt_cpy, va_cpy); + + va_end(va_cpy); + for (uint32_t i = 0; i < alloc_idx; i++) { + free(allocs[i]); + allocs[i] = nullptr; + } + free(fmt_cpy); + return ret; +} + +int32_t ext_snprintf_P(char * buf, size_t buf_len, const char * fmt, ...) { + va_list va; + va_start(va, fmt); + + int32_t ret = ext_vsnprintf_P(buf, buf_len, fmt, va); + va_end(va); + return ret; +} diff --git a/lib/default/Ext-printf/src/ext_printf.h b/lib/default/Ext-printf/src/ext_printf.h new file mode 100644 index 000000000..6dc4d4061 --- /dev/null +++ b/lib/default/Ext-printf/src/ext_printf.h @@ -0,0 +1,32 @@ +/* + ext_printf.ino - Extended printf for Arduino objects + + Copyright (C) 2021 Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EXT_PRINTF_H +#define EXT_PRINTF_H + +#include +#include +#include + +int32_t ext_vsnprintf_P(char * buf, size_t buf_len, const char * fmt_P, va_list va); +int32_t ext_snprintf_P(char * buf, size_t buf_len, const char * fmt, ...); + +// void test_ext_snprintf_P(void); + +#endif // EXT_PRINTF_H \ No newline at end of file diff --git a/lib/default/Ext-printf/test/test_ext_printf.cpp b/lib/default/Ext-printf/test/test_ext_printf.cpp new file mode 100644 index 000000000..d29e9e8e7 --- /dev/null +++ b/lib/default/Ext-printf/test/test_ext_printf.cpp @@ -0,0 +1,106 @@ +/* + ext_printf.ino - Extended printf for Arduino objects + + Copyright (C) 2021 Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "ext_printf.h" +#include + +// DEBUG only + +// String test_string(void) { +// String s("This is the string"); + +// return s; +// } + +// String f_str(void) { return String("foobar"); } +// String k_str("foobar"); + +// void f1(String s) { +// Serial.printf("> %s\n", s.c_str()); +// } +// void f2(void) { +// f1(f_str()); +// } + +// void test_snprintf1(void) { +// char c[100]; +// snprintf_P(c, sizeof(c), PSTR("s1=%s, s2=%s"), k_str.c_str(), f_str().c_str()); +// } +// void test_snprintf2(void) { +// char c[100]; +// ext_snprintf_P(c, sizeof(c), PSTR("s1=%_s, s2=%_S"), &k_str, &f_str, &ResponseAppendTHD); +// } +void test_ext_snprintf_P(void) { +// test_snprintf1(); +// test_snprintf2(); +// if (0) { + // // testVarArg2("", 1, 2, 3, 4, 5, 6, 7, 8); + + char c[128]; + float fpi=-3333.1415926535f; + float f3 = 3333; + float f31 = 3333.1; + ext_snprintf_P(c, sizeof(c), "Int1 = %d, ip=%_I", 1, 0x10203040); + Serial.printf("--> out=%s\n", c); + + ext_snprintf_P(c, sizeof(c), "Float default=%_f %_f", &f3, &fpi); + Serial.printf("--> out=%s\n", c); + + ext_snprintf_P(c, sizeof(c), "Float default=%1_f, int(3)=%4_f, int(3)=%-4_f, int(3)=%-4_f, 6dec=%-8_f", &fpi, &f3, &f3, &f31, &fpi); + Serial.printf("--> out=%s\n", c); + uint64_t u641 = 0x1122334455667788LL; + uint64_t u642 = 0x0123456789ABCDEFLL; + uint64_t u643 = 0xFEDCBA9876543210LL; + ext_snprintf_P(c, sizeof(c), "Int64 0x%_X 0x%_X 0x%_X", &u641, &u642, &u643); + Serial.printf("--> out=%s\n", c); + + // String string("Foobar"); + // ext_snprintf_P(c, sizeof(c), "String 0x%08X %_s", &string, &string); + // Serial.printf("--> out=%s\n", c); + + // ext_snprintf_P(c, sizeof(c), "StringFunc 0x%08X %_S", &test_string, &test_string); + // Serial.printf("--> out=%s\n", c); + + // uint64_t u64 = 0x123456789ABCDEFLL; + // testVarArg2("", u64, 2, 3, 4, 5, 6, 7, 8); + + // // Serial.printf("+++ ld=%ld, lld=%lld\n", 1,2,3,4); + // // testVarArg("", 1, 2, 3, 4, 5, 6, 7, 8); + // } + // tprintf("%s", 12, "14"); +} + + +// void tprintf(const char* format) // base function +// { +// Serial.printf("%s\n", format); +// } + +// template +// void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function +// { +// for ( ; *format != '\0'; format++ ) { +// if ( *format == '%' ) { +// Serial.printf("%d", (uint32_t) value); +// tprintf(format+1, Fargs...); // recursive call +// return; +// } +// Serial.printf("%s", format); +// } +// } diff --git a/tasmota/support.ino b/tasmota/support.ino index 5b96069e2..c3c3d46a9 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -322,26 +322,6 @@ int TextToInt(char *str) return strtol(str, &p, radix); } -char* ulltoa(unsigned long long value, char *str, int radix) -{ - char digits[64]; - char *dst = str; - int i = 0; - -// if (radix < 2 || radix > 36) { radix = 10; } - - do { - int n = value % radix; - digits[i++] = (n < 10) ? (char)n+'0' : (char)n-10+'A'; - value /= radix; - } while (value != 0); - - while (i > 0) { *dst++ = digits[--i]; } - - *dst = 0; - return str; -} - // see https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c // char* ToHex_P(unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween = '\0'); in tasmota_globals.h char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, char inbetween) @@ -363,24 +343,6 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c return out; } -char* Uint64toHex(uint64_t value, char *str, uint16_t bits) -{ - ulltoa(value, str, 16); // Get 64bit value - - int fill = 8; - if ((bits > 3) && (bits < 65)) { - fill = bits / 4; // Max 16 - if (bits % 4) { fill++; } - } - int len = strlen(str); - fill -= len; - if (fill > 0) { - memmove(str + fill, str, len +1); - memset(str, '0', fill); - } - return str; -} - 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) @@ -1229,7 +1191,7 @@ int Response_P(const char* format, ...) // Content send snprintf_P char d // This uses char strings. Be aware of sending %% if % is needed va_list args; va_start(args, format); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), format, args); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), format, args); va_end(args); return len; } @@ -1243,7 +1205,7 @@ int ResponseTime_P(const char* format, ...) // Content send snprintf_P char d ResponseGetTime(Settings.flag2.time_format, TasmotaGlobal.mqtt_data); int mlen = strlen(TasmotaGlobal.mqtt_data); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data + mlen, sizeof(TasmotaGlobal.mqtt_data) - mlen, format, args); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data + mlen, sizeof(TasmotaGlobal.mqtt_data) - mlen, format, args); va_end(args); return len + mlen; } @@ -1254,7 +1216,7 @@ int ResponseAppend_P(const char* format, ...) // Content send snprintf_P char d va_list args; va_start(args, format); int mlen = strlen(TasmotaGlobal.mqtt_data); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data + mlen, sizeof(TasmotaGlobal.mqtt_data) - mlen, format, args); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data + mlen, sizeof(TasmotaGlobal.mqtt_data) - mlen, format, args); va_end(args); return len + mlen; } @@ -1270,18 +1232,29 @@ int ResponseAppendTime(void) return ResponseAppendTimeFormat(Settings.flag2.time_format); } +// int ResponseAppendTHD(float f_temperature, float f_humidity) +// { +// char temperature[FLOATSZ]; +// dtostrfd(f_temperature, Settings.flag2.temperature_resolution, temperature); +// char humidity[FLOATSZ]; +// dtostrfd(f_humidity, Settings.flag2.humidity_resolution, humidity); +// char dewpoint[FLOATSZ]; +// dtostrfd(CalcTempHumToDew(f_temperature, f_humidity), Settings.flag2.temperature_resolution, dewpoint); + +// return ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_DEWPOINT "\":%s"), temperature, humidity, dewpoint); +// } + int ResponseAppendTHD(float f_temperature, float f_humidity) { - char temperature[FLOATSZ]; - dtostrfd(f_temperature, Settings.flag2.temperature_resolution, temperature); - char humidity[FLOATSZ]; - dtostrfd(f_humidity, Settings.flag2.humidity_resolution, humidity); - char dewpoint[FLOATSZ]; - dtostrfd(CalcTempHumToDew(f_temperature, f_humidity), Settings.flag2.temperature_resolution, dewpoint); + float dewpoint = CalcTempHumToDew(f_temperature, f_humidity); - return ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s,\"" D_JSON_DEWPOINT "\":%s"), temperature, humidity, dewpoint); + return ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_HUMIDITY "\":%*_f,\"" D_JSON_DEWPOINT "\":%*_f"), + Settings.flag2.temperature_resolution, &f_temperature, + Settings.flag2.humidity_resolution, &f_humidity, + Settings.flag2.temperature_resolution, &dewpoint); } + int ResponseJsonEnd(void) { return ResponseAppend_P(PSTR("}")); @@ -2209,7 +2182,7 @@ void AddLog(uint32_t loglevel, PGM_P formatP, ...) { va_list arg; va_start(arg, formatP); - uint32_t len = vsnprintf_P(log_data, LOGSZ +1, formatP, arg); + uint32_t len = ext_vsnprintf_P(log_data, LOGSZ +1, formatP, arg); va_end(arg); if (len > LOGSZ) { strcat(log_data, "..."); } // Actual data is more @@ -2243,7 +2216,7 @@ void AddLog_Debug(PGM_P formatP, ...) va_list arg; va_start(arg, formatP); - uint32_t len = vsnprintf_P(log_data, sizeof(log_data), formatP, arg); + uint32_t len = ext_vsnprintf_P(log_data, sizeof(log_data), formatP, arg); va_end(arg); AddLogData(LOG_LEVEL_DEBUG, log_data); diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index f934a60a2..1a956fd0c 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -52,6 +52,7 @@ #include // Ota #include // Ota #include // Webserver, Updater +#include #include #include #ifdef USE_ARDUINO_OTA diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 68d1eb0dc..991394762 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -644,7 +644,7 @@ void WSContentSend_P(const char* formatP, ...) // Content send snprintf_P ch // This uses char strings. Be aware of sending %% if % is needed va_list arg; va_start(arg, formatP); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); va_end(arg); #ifdef DEBUG_TASMOTA_CORE @@ -662,7 +662,7 @@ void WSContentSend_PD(const char* formatP, ...) // Content send snprintf_P ch // This uses char strings. Be aware of sending %% if % is needed va_list arg; va_start(arg, formatP); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); va_end(arg); #ifdef DEBUG_TASMOTA_CORE @@ -722,7 +722,7 @@ void WSContentSendStyle_P(const char* formatP, ...) // This uses char strings. Be aware of sending %% if % is needed va_list arg; va_start(arg, formatP); - int len = vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); + int len = ext_vsnprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), formatP, arg); va_end(arg); #ifdef DEBUG_TASMOTA_CORE @@ -1710,10 +1710,9 @@ void HandleWifiConfiguration(void) } #else // No USE_ENHANCED_GUI_WIFI_SCAN // remove duplicates ( must be RSSI sorted ) - String cssid; for (uint32_t i = 0; i < n; i++) { if (-1 == indices[i]) { continue; } - cssid = WiFi.SSID(indices[i]); + String cssid = WiFi.SSID(indices[i]); uint32_t cschn = WiFi.channel(indices[i]); for (uint32_t j = i + 1; j < n; j++) { if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) { @@ -2111,9 +2110,9 @@ void HandleInformation(void) } } if (!TasmotaGlobal.global_state.network_down) { - WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), IPAddress(Settings.ipv4_address[1]).toString().c_str()); - WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%s"), IPAddress(Settings.ipv4_address[2]).toString().c_str()); - WSContentSend_P(PSTR("}1" D_DNS_SERVER "}2%s"), IPAddress(Settings.ipv4_address[3]).toString().c_str()); + WSContentSend_P(PSTR("}1" D_GATEWAY "}2%_I"), Settings.ipv4_address[1]); + WSContentSend_P(PSTR("}1" D_SUBNET_MASK "}2%_I"), Settings.ipv4_address[2]); + WSContentSend_P(PSTR("}1" D_DNS_SERVER "}2%_I"), Settings.ipv4_address[3]); } if ((WiFi.getMode() >= WIFI_AP) && (static_cast(WiFi.softAPIP()) != 0)) { WSContentSend_P(PSTR("}1
}2
")); diff --git a/tasmota/xdrv_05_irremote.ino b/tasmota/xdrv_05_irremote.ino index 66760f865..0cacb2161 100644 --- a/tasmota/xdrv_05_irremote.ino +++ b/tasmota/xdrv_05_irremote.ino @@ -76,6 +76,44 @@ const char kIrRemoteCommands[] PROGMEM = "|" D_CMND_IRSEND ; void (* const IrRemoteCommand[])(void) PROGMEM = { &CmndIrSend }; +char* ulltoa(unsigned long long value, char *str, int radix) +{ + char digits[64]; + char *dst = str; + int i = 0; + +// if (radix < 2 || radix > 36) { radix = 10; } + + do { + int n = value % radix; + digits[i++] = (n < 10) ? (char)n+'0' : (char)n-10+'A'; + value /= radix; + } while (value != 0); + + while (i > 0) { *dst++ = digits[--i]; } + + *dst = 0; + return str; +} + +char* Uint64toHex(uint64_t value, char *str, uint16_t bits) +{ + ulltoa(value, str, 16); // Get 64bit value + + int fill = 8; + if ((bits > 3) && (bits < 65)) { + fill = bits / 4; // Max 16 + if (bits % 4) { fill++; } + } + int len = strlen(str); + fill -= len; + if (fill > 0) { + memmove(str + fill, str, len +1); + memset(str, '0', fill); + } + return str; +} + /*********************************************************************************************\ * Class used to make a compact IR Raw format. * @@ -291,10 +329,10 @@ uint32_t IrRemoteCmndIrSendJson(void) char protocol_text[20]; int protocol_code = GetCommandCode(protocol_text, sizeof(protocol_text), protocol, kIrRemoteProtocols); - char dvalue[64]; - char hvalue[20]; - AddLog(LOG_LEVEL_DEBUG, PSTR("IRS: protocol_text %s, protocol %s, bits %d, data %s (0x%s), repeat %d, protocol_code %d"), - protocol_text, protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat, protocol_code); + // char dvalue[64]; + // char hvalue[20]; + // AddLog(LOG_LEVEL_DEBUG, PSTR("IRS: protocol_text %s, protocol %s, bits %d, data %s (0x%s), repeat %d, protocol_code %d"), + // protocol_text, protocol, bits, ulltoa(data, dvalue, 10), Uint64toHex(data, hvalue, bits), repeat, protocol_code); #ifdef USE_IR_RECEIVE if (!IR_RCV_WHILE_SENDING && (irrecv != nullptr)) { irrecv->disableIRIn(); } diff --git a/tasmota/xdrv_05_irremote_full.ino b/tasmota/xdrv_05_irremote_full.ino index 745529e3d..c88c19d8a 100644 --- a/tasmota/xdrv_05_irremote_full.ino +++ b/tasmota/xdrv_05_irremote_full.ino @@ -235,57 +235,36 @@ String sendACJsonState(const stdAc::state_t &state) { return payload; } -String sendIRJsonState(const struct decode_results &results) { - String json("{"); - json += "\"" D_JSON_IR_PROTOCOL "\":\""; - json += typeToString(results.decode_type); - json += "\",\"" D_JSON_IR_BITS "\":"; - json += results.bits; +void sendIRJsonState(const struct decode_results &results) { + Response_P(PSTR("\"" D_JSON_IR_PROTOCOL "\":\"%s\",\"" D_JSON_IR_BITS "\":%d"), + typeToString(results.decode_type).c_str(), + results.bits); if (hasACState(results.decode_type)) { - json += ",\"" D_JSON_IR_DATA "\":\""; - json += resultToHexidecimal(&results); - json += "\""; + ResponseAppend_P(PSTR(",\"" D_JSON_IR_DATA "\":\"%s\""), + resultToHexidecimal(&results).c_str()); } else { - if (UNKNOWN != results.decode_type) { - json += ",\"" D_JSON_IR_DATA "\":"; - } else { - json += ",\"" D_JSON_IR_HASH "\":"; - } + ResponseAppend_P(PSTR(",\"%s\":"), UNKNOWN != results.decode_type ? PSTR(D_JSON_IR_DATA) : PSTR(D_JSON_IR_HASH)); if (Settings.flag.ir_receive_decimal) { // SetOption29 - IR receive data format - char svalue[32]; - ulltoa(results.value, svalue, 10); - json += svalue; + ResponseAppend_P(PSTR("%u"), (uint32_t) results.value); } else { - char hvalue[64]; if (UNKNOWN != results.decode_type) { - Uint64toHex(results.value, hvalue, results.bits); // Get 64bit value as hex 0x00123456 - json += "\"0x"; - json += hvalue; - json += "\",\"" D_JSON_IR_DATALSB "\":\"0x"; - Uint64toHex(reverseBitsInBytes64(results.value), hvalue, results.bits); // Get 64bit value as hex 0x00123456, LSB - json += hvalue; - json += "\""; + uint64_t reverse = reverseBitsInBytes64(results.value); + ResponseAppend_P(PSTR("\"0x%_X\",\"" D_JSON_IR_DATALSB "\":\"0x%_X\""), + &results.value, &reverse); } else { // UNKNOWN - Uint64toHex(results.value, hvalue, 32); // Unknown is always 32 bits - json += "\"0x"; - json += hvalue; - json += "\""; + ResponseAppend_P(PSTR("\"0x08X\""), (uint32_t) results.value); // Unknown is always 32 bits } } } - json += ",\"" D_JSON_IR_REPEAT "\":"; - json += results.repeat; + ResponseAppend_P(PSTR(",\"" D_JSON_IR_REPEAT "\":%d"), results.repeat); stdAc::state_t new_state; if (IRAcUtils::decodeToState(&results, &new_state, irhvac_stateful && irac_prev_state.protocol == results.decode_type ? &irac_prev_state : nullptr)) { // we have a decoded state - json += ",\"" D_CMND_IRHVAC "\":"; - json += sendACJsonState(new_state); + ResponseAppend_P(PSTR(",\"" D_CMND_IRHVAC "\":%s"), sendACJsonState(new_state).c_str()); irac_prev_state = new_state; // store for next time } - - return json; } void IrReceiveCheck(void) @@ -298,7 +277,8 @@ void IrReceiveCheck(void) // if ((now - ir_lasttime > IR_TIME_AVOID_DUPLICATE) && (UNKNOWN != results.decode_type) && (results.bits > 0)) { if (now - ir_lasttime > IR_TIME_AVOID_DUPLICATE) { ir_lasttime = now; - Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":%s"), sendIRJsonState(results).c_str()); + Response_P(PSTR("{\"" D_JSON_IRRECEIVED "\":{")); + sendIRJsonState(results); IRRawTable raw_table; bool prev_number = false; // was the previous value a number, meaning we may need a comma prefix diff --git a/tasmota/xdrv_23_zigbee_1z_libs.ino b/tasmota/xdrv_23_zigbee_1z_libs.ino index a228798e2..7531b003e 100644 --- a/tasmota/xdrv_23_zigbee_1z_libs.ino +++ b/tasmota/xdrv_23_zigbee_1z_libs.ino @@ -399,9 +399,7 @@ void Z_attribute::setHex32(uint32_t _val) { } void Z_attribute::setHex64(uint64_t _val) { char hex[22]; - hex[0] = '0'; // prefix with '0x' - hex[1] = 'x'; - Uint64toHex(_val, &hex[2], 64); + ext_snprintf_P(hex, sizeof(hex), PSTR("0x%_X"), &_val); setStr(hex); } diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 1ac9adb23..3c1d3e79b 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -115,12 +115,10 @@ int32_t EZ_NetworkParameters(int32_t res, class SBuffer &buf) { // localIEEEAddr = long_adr; // localShortAddr = short_adr; - char hex[20]; - Uint64toHex(localIEEEAddr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"DeviceType\":%d}}"), - ZIGBEE_STATUS_EZ_INFO, hex, localShortAddr, node_type); + ZIGBEE_STATUS_EZ_INFO, &localIEEEAddr, localShortAddr, node_type); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); @@ -276,12 +274,10 @@ int32_t Z_EZSPNetworkParameters(int32_t res, class SBuffer &buf) { // localIEEEAddr = long_adr; // localShortAddr = short_adr; - char hex[20]; - Uint64toHex(localIEEEAddr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"DeviceType\":%d}}"), - ZIGBEE_STATUS_EZ_INFO, hex, localShortAddr, node_type); + ZIGBEE_STATUS_EZ_INFO, &localIEEEAddr, localShortAddr, node_type); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); @@ -313,13 +309,11 @@ int32_t ZNP_ReceiveDeviceInfo(int32_t res, class SBuffer &buf) { localIEEEAddr = long_adr; localShortAddr = short_adr; - char hex[20]; - Uint64toHex(long_adr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"DeviceType\":%d,\"DeviceState\":%d" ",\"NumAssocDevices\":%d"), - ZIGBEE_STATUS_CC_INFO, hex, short_adr, device_type, device_state, + ZIGBEE_STATUS_CC_INFO, &long_adr, short_adr, device_type, device_state, device_associated); if (device_associated > 0) { // If there are devices registered in CC2530, print the list @@ -772,13 +766,11 @@ int32_t Z_ReceiveIEEEAddr(int32_t res, const class SBuffer &buf) { if (0 == status) { // SUCCESS zigbee_devices.updateDevice(nwkAddr, ieeeAddr); zigbee_devices.deviceWasReached(nwkAddr); - char hex[20]; - Uint64toHex(ieeeAddr, hex, 64); // Ping response const char * friendlyName = zigbee_devices.getFriendlyName(nwkAddr); Response_P(PSTR("{\"" D_JSON_ZIGBEE_PING "\":{\"" D_JSON_ZIGBEE_DEVICE "\":\"0x%04X\"" - ",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%s\""), nwkAddr, hex); + ",\"" D_JSON_ZIGBEE_IEEE "\":\"0x%_X\""), nwkAddr, &ieeeAddr); if (friendlyName) { ResponseAppend_P(PSTR(",\"" D_JSON_ZIGBEE_NAME "\":\"%s\""), friendlyName); } @@ -899,12 +891,10 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { // device is reachable zigbee_devices.deviceWasReached(nwkAddr); - char hex[20]; - Uint64toHex(ieeeAddr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"PowerSource\":%s,\"ReceiveWhenIdle\":%s,\"Security\":%s}}"), - ZIGBEE_STATUS_DEVICE_ANNOUNCE, hex, nwkAddr, + ZIGBEE_STATUS_DEVICE_ANNOUNCE, &ieeeAddr, nwkAddr, (capabilities & 0x04) ? PSTR("true") : PSTR("false"), (capabilities & 0x08) ? PSTR("true") : PSTR("false"), (capabilities & 0x40) ? PSTR("true") : PSTR("false") @@ -934,12 +924,10 @@ int32_t ZNP_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { // device is reachable zigbee_devices.deviceWasReached(srcAddr); - char hex[20]; - Uint64toHex(ieeeAddr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"ParentNetwork\":\"0x%04X\"}}"), - ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw + ZIGBEE_STATUS_DEVICE_INDICATION, &ieeeAddr, srcAddr, parentNw ); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); @@ -1187,9 +1175,7 @@ int32_t Z_Mgmt_Lqi_Bind_Rsp(int32_t res, const class SBuffer &buf, boolean lqi) if (Z_Addr_Group == addrmode) { // Group address mode ResponseAppend_P(PSTR("\"ToGroup\":%d}"), group); } else if (Z_Addr_IEEEAddress == addrmode) { // IEEE address mode - char hex[20]; - Uint64toHex(dstaddr, hex, 64); - ResponseAppend_P(PSTR("\"ToDevice\":\"0x%s\",\"ToEndpoint\":%d}"), hex, dstep); + ResponseAppend_P(PSTR("\"ToDevice\":\"0x%_X\",\"ToEndpoint\":%d}"), &dstaddr, dstep); } } @@ -1268,9 +1254,7 @@ int32_t EZ_ParentAnnceRsp(int32_t res, const class SBuffer &buf, bool rsp) { if (i > 0) { ResponseAppend_P(PSTR(",")); } - char hex[20]; - Uint64toHex(child_ieee, hex, 64); - ResponseAppend_P(PSTR("\"0x%s\""), hex); + ResponseAppend_P(PSTR("\"0x%_X\""), &child_ieee); } ResponseAppend_P(PSTR("]}}")); @@ -1596,14 +1580,12 @@ int32_t EZ_ReceiveTCJoinHandler(int32_t res, const class SBuffer &buf) { if (EMBER_DEVICE_LEFT != status) { // ignore message if the device is leaving zigbee_devices.updateDevice(srcAddr, ieeeAddr); - char hex[20]; - Uint64toHex(ieeeAddr, hex, 64); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" - "\"Status\":%d,\"IEEEAddr\":\"0x%s\",\"ShortAddr\":\"0x%04X\"" + "\"Status\":%d,\"IEEEAddr\":\"0x%_X\",\"ShortAddr\":\"0x%04X\"" ",\"ParentNetwork\":\"0x%04X\"" ",\"JoinStatus\":%d,\"Decision\":%d" "}}"), - ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw, + ZIGBEE_STATUS_DEVICE_INDICATION, &ieeeAddr, srcAddr, parentNw, status, decision ); diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index af66a4972..4455c5b0a 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -1643,25 +1643,18 @@ void CmndZbConfig(void) { } // display the current or new configuration - char hex_ext_panid[20] = "0x"; - Uint64toHex(zb_ext_panid, &hex_ext_panid[2], 64); - char hex_precfgkey_l[20] = "0x"; - Uint64toHex(zb_precfgkey_l, &hex_precfgkey_l[2], 64); - char hex_precfgkey_h[20] = "0x"; - Uint64toHex(zb_precfgkey_h, &hex_precfgkey_h[2], 64); - // {"ZbConfig":{"Channel":11,"PanID":"0x1A63","ExtPanID":"0xCCCCCCCCCCCCCCCC","KeyL":"0x0F0D0B0907050301L","KeyH":"0x0D0C0A0806040200L"}} Response_P(PSTR("{\"" D_PRFX_ZB D_JSON_ZIGBEE_CONFIG "\":{" "\"Channel\":%d" ",\"PanID\":\"0x%04X\"" - ",\"ExtPanID\":\"%s\"" - ",\"KeyL\":\"%s\"" - ",\"KeyH\":\"%s\"" + ",\"ExtPanID\":\"0x%_X\"" + ",\"KeyL\":\"0x%_X\"" + ",\"KeyH\":\"0x%_X\"" ",\"TxRadio\":%d" "}}"), zb_channel, zb_pan_id, - hex_ext_panid, - hex_precfgkey_l, hex_precfgkey_h, + &zb_ext_panid, + &zb_precfgkey_l, &zb_precfgkey_h, zb_txradio_dbm); }