Merge pull request #10680 from s-hadinger/ext_printf2

Added ext_snprintf to support extended types
This commit is contained in:
Theo Arends 2021-01-24 17:48:06 +01:00 committed by GitHub
commit 2cf463cc32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 553 additions and 146 deletions

View File

@ -0,0 +1,7 @@
name=Ext-printf
version=1.0
author=Stephan Hadinger
maintainer=Stephan <stephan.hadinger@gmail.com>
sentence=Extension of snprintf() and vsnprintf()
paragraph=This library provides extended types support for snprintf (float, uint64_t)
architectures=esp8266, esp32

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ext_printf.h"
#include <Arduino.h>
#include <IPAddress.h>
/*********************************************************************************************\
* 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef EXT_PRINTF_H
#define EXT_PRINTF_H
#include <cstddef>
#include <cstdint>
#include <cstdarg>
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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "ext_printf.h"
#include <Arduino.h>
// 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<typename T, typename... Targs>
// 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);
// }
// }

View File

@ -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);

View File

@ -52,6 +52,7 @@
#include <ESP8266HTTPClient.h> // Ota
#include <ESP8266httpUpdate.h> // Ota
#include <StreamString.h> // Webserver, Updater
#include <ext_printf.h>
#include <JsonParser.h>
#include <JsonGenerator.h>
#ifdef USE_ARDUINO_OTA

View File

@ -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<uint32_t>(WiFi.softAPIP()) != 0)) {
WSContentSend_P(PSTR("}1<hr/>}2<hr/>"));

View File

@ -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(); }

View File

@ -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

View File

@ -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);
}

View File

@ -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
);

View File

@ -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);
}