Tasmota/sonoff/support.ino
Andrea Pede 5b5272e684
Missing the end pointer reference solves #5760
Exiting from the unescape call the content in buffer still contains the original string. The result of unescape elaboration works well, but the rest of the string is still there and in the printf in the main program infact is printed overlapped to the previous content.
TY MauroS for the hint.
2019-05-09 11:26:20 +02:00

1518 lines
40 KiB
C++

/*
support.ino - support for Sonoff-Tasmota
Copyright (C) 2019 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/>.
*/
IPAddress syslog_host_addr; // Syslog host IP address
uint32_t syslog_host_hash = 0; // Syslog host name hash
/*********************************************************************************************\
* Watchdog extension (https://github.com/esp8266/Arduino/issues/1532)
\*********************************************************************************************/
#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() ICACHE_RAM_ATTR;
#endif // USE_WS2812_DMA
#ifdef USE_KNX
bool knx_started = false;
#endif // USE_KNX
void OsWatchTicker(void)
{
unsigned long t = millis();
unsigned long last_run = abs(t - oswatch_last_loop_time);
#ifdef DEBUG_THEO
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d, last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), last_run);
#endif // DEBUG_THEO
if (last_run >= (OSWATCH_RESET_TIME * 1000)) {
// AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_OSWATCH " " D_BLOCKED_LOOP ". " D_RESTARTING)); // Save iram space
RtcSettings.oswatch_blocked_loop = 1;
RtcSettingsSave();
// ESP.restart(); // normal reboot
ESP.reset(); // hard reset
}
}
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
}
String GetResetReason(void)
{
char buff[32];
if (oswatch_blocked_loop) {
strncpy_P(buff, PSTR(D_JSON_BLOCKED_LOOP), sizeof(buff));
return String(buff);
} else {
return ESP.getResetReason();
}
}
bool OsWatchBlockedLoop(void)
{
return oswatch_blocked_loop;
}
/*********************************************************************************************\
* Miscellaneous
\*********************************************************************************************/
#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
// Functions not available in 2.3.0
// http://clc-wiki.net/wiki/C_standard_library:string.h:memchr
void* memchr(const void* ptr, int value, size_t num)
{
unsigned char *p = (unsigned char*)ptr;
while (num--) {
if (*p != (unsigned char)value) {
p++;
} else {
return p;
}
}
return 0;
}
// http://clc-wiki.net/wiki/C_standard_library:string.h:strcspn
// Get span until any character in string
size_t strcspn(const char *str1, const char *str2)
{
size_t ret = 0;
while (*str1) {
if (strchr(str2, *str1)) { // Slow
return ret;
} else {
str1++;
ret++;
}
}
return ret;
}
// https://opensource.apple.com/source/Libc/Libc-583/stdlib/FreeBSD/strtoull.c
// Convert a string to an unsigned long long integer
#ifndef __LONG_LONG_MAX__
#define __LONG_LONG_MAX__ 9223372036854775807LL
#endif
#ifndef ULLONG_MAX
#define ULLONG_MAX (__LONG_LONG_MAX__ * 2ULL + 1)
#endif
unsigned long long strtoull(const char *__restrict nptr, char **__restrict endptr, int base)
{
const char *s = nptr;
char c;
do { c = *s++; } while (isspace((unsigned char)c)); // Trim leading spaces
int neg = 0;
if (c == '-') { // Set minus flag and/or skip sign
neg = 1;
c = *s++;
} else {
if (c == '+') {
c = *s++;
}
}
if ((base == 0 || base == 16) && (c == '0') && (*s == 'x' || *s == 'X')) { // Set Hexadecimal
c = s[1];
s += 2;
base = 16;
}
if (base == 0) { base = (c == '0') ? 8 : 10; } // Set Octal or Decimal
unsigned long long acc = 0;
int any = 0;
if (base > 1 && base < 37) {
unsigned long long cutoff = ULLONG_MAX / base;
int cutlim = ULLONG_MAX % base;
for ( ; ; c = *s++) {
if (c >= '0' && c <= '9')
c -= '0';
else if (c >= 'A' && c <= 'Z')
c -= 'A' - 10;
else if (c >= 'a' && c <= 'z')
c -= 'a' - 10;
else
break;
if (c >= base)
break;
if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim))
any = -1;
else {
any = 1;
acc *= base;
acc += c;
}
}
if (any < 0) {
acc = ULLONG_MAX; // Range error
}
else if (any && neg) {
acc = -acc;
}
}
if (endptr != nullptr) { *endptr = (char *)(any ? s - 1 : nptr); }
return acc;
}
#endif // ARDUINO_ESP8266_RELEASE_2_3_0
// 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;
}
// Function to return a substring defined by a delimiter at an index
char* subStr(char* dest, char* str, const char *delim, int index)
{
char *act;
char *sub = nullptr;
char *ptr;
int i;
// Since strtok consumes the first arg, make a copy
strncpy(dest, str, strlen(str)+1);
for (i = 1, act = dest; i <= index; i++, act = nullptr) {
sub = strtok_r(act, delim, &ptr);
if (sub == nullptr) break;
}
sub = Trim(sub);
return sub;
}
double CharToDouble(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;
while ((*pt != '\0') && isblank(*pt)) { pt++; } // Trim leading spaces
signed char sign = 1;
if (*pt == '-') { sign = -1; }
if (*pt == '-' || *pt=='+') { pt++; } // Skip any sign
double left = 0;
if (*pt != '.') {
left = atoi(pt); // Get left part
while (isdigit(*pt)) { pt++; } // Skip number
}
double right = 0;
if (*pt == '.') {
pt++;
right = atoi(pt); // Decimal part
while (isdigit(*pt)) {
pt++;
right /= 10.0;
}
}
double 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* ulltoa(unsigned long long value, char *str, int radix)
{
char digits[64];
char *dst = str;
int i = 0;
int n = 0;
// if (radix < 2 || radix > 36) { radix = 10; }
do {
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* 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)
strcpy(s, "null");
return s;
} else {
return dtostrf(number, 1, prec, s);
}
}
char* Unescape(char* buffer, uint16_t* size)
{
uint8_t* read = (uint8_t*)buffer;
uint8_t* write = (uint8_t*)buffer;
int16_t start_size = *size;
int16_t end_size = *size;
uint8_t che = 0;
// AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)buffer, *size);
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
// AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t*)buffer, *size);
return buffer;
}
char* RemoveSpace(char* p)
{
char* write = p;
char* read = p;
char ch = '.';
while (ch != '\0') {
ch = *read++;
if (!isspace(ch)) {
*write++ = ch;
}
}
// *write = '\0'; // Removed 20190223 as it buffer overflows on no isspace found - no need either
return p;
}
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* Trim(char* p)
{
while ((*p != '\0') && isblank(*p)) { p++; } // Trim leading spaces
char* q = p + strlen(p) -1;
while ((q >= p) && isblank(*q)) { q--; } // Trim trailing spaces
q++;
*q = '\0';
return p;
}
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;
}
void SetShortcut(char* str, uint8_t action)
{
if ('\0' != str[0]) { // There must be at least one character in the buffer
str[0] = '0' + action; // SC_CLEAR, SC_DEFAULT, SC_USER
str[1] = '\0';
}
}
uint8_t Shortcut(const char* str)
{
uint8_t result = 10;
if ('\0' == str[1]) { // Only allow single character input for shortcut
if (('"' == str[0]) || ('0' == str[0])) {
result = SC_CLEAR;
} else {
result = atoi(str); // 1 = SC_DEFAULT, 2 = SC_USER
if (0 == result) {
result = 10;
}
}
}
return result;
}
bool ValidIpAddress(const char* str)
{
const char* p = str;
while (*p && ((*p == '.') || ((*p >= '0') && (*p <= '9')))) { p++; }
return (*p == '\0');
}
bool ParseIp(uint32_t* addr, const char* str)
{
uint8_t *part = (uint8_t*)addr;
uint8_t i;
*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);
}
void MakeValidMqtt(uint8_t option, char* str)
{
// option 0 = replace by underscore
// option 1 = delete character
uint16_t i = 0;
while (str[i] > 0) {
// if ((str[i] == '/') || (str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) {
if ((str[i] == '+') || (str[i] == '#') || (str[i] == ' ')) {
if (option) {
uint16_t j = i;
while (str[j] > 0) {
str[j] = str[j +1];
j++;
}
i--;
} else {
str[i] = '_';
}
}
i++;
}
}
// Function to parse & check if version_str is newer than our currently installed version.
bool NewerVersion(char* version_str)
{
uint32_t version = 0;
uint8_t i = 0;
char *str_ptr;
char* version_dup = strdup(version_str); // Duplicate the version_str as strtok_r will modify it.
if (!version_dup) {
return false; // Bail if we can't duplicate. Assume bad.
}
// 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);
// The fields in a version string can only range from 0-255.
if ((field < 0) || (field > 255)) {
free(version_dup);
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;
version = (version << 8) + field;
i++;
}
}
free(version_dup); // We no longer need this.
// 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);
}
char* GetPowerDevice(char* dest, uint8_t idx, size_t size, uint8_t option)
{
char sidx[8];
strncpy_P(dest, S_RSLT_POWER, size); // POWER
if ((devices_present + option) > 1) {
snprintf_P(sidx, sizeof(sidx), PSTR("%d"), idx); // x
strncat(dest, sidx, size - strlen(dest) -1); // POWERx
}
return dest;
}
char* GetPowerDevice(char* dest, uint8_t idx, size_t size)
{
return GetPowerDevice(dest, idx, size, 0);
}
float ConvertTemp(float c)
{
float result = c;
global_update = uptime;
global_temperature = c;
if (!isnan(c) && Settings.flag.temperature_conversion) {
result = c * 1.8 + 32; // Fahrenheit
}
return result;
}
float ConvertTempToCelsius(float c)
{
float result = c;
if (!isnan(c) && Settings.flag.temperature_conversion) {
result = (c - 32) / 1.8; // Celsius
}
return result;
}
char TempUnit(void)
{
return (Settings.flag.temperature_conversion) ? 'F' : 'C';
}
float ConvertHumidity(float h)
{
global_update = uptime;
global_humidity = h;
return h;
}
float ConvertPressure(float p)
{
float result = p;
global_update = uptime;
global_pressure = p;
if (!isnan(p) && Settings.flag.pressure_conversion) {
result = p * 0.75006375541921; // mmHg
}
return result;
}
String PressureUnit(void)
{
return (Settings.flag.pressure_conversion) ? String(D_UNIT_MILLIMETER_MERCURY) : String(D_UNIT_PRESSURE);
}
void ResetGlobalValues(void)
{
if ((uptime - global_update) > GLOBAL_VALUES_VALID) { // Reset after 5 minutes
global_update = 0;
global_temperature = 0;
global_humidity = 0;
global_pressure = 0;
}
}
double FastPrecisePow(double a, double b)
{
// https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp/
// calculate approximation with fraction of the exponent
int e = (int)b;
union {
double d;
int x[2];
} u = { a };
u.x[1] = (int)((b - e) * (u.x[1] - 1072632447) + 1072632447);
u.x[0] = 0;
// exponentiation by squaring with the exponent's integer part
// double r = u.d makes everything much slower, not sure why
double r = 1.0;
while (e) {
if (e & 1) {
r *= a;
}
a *= a;
e >>= 1;
}
return r * u.d;
}
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, uint16_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;
}
int GetStateNumber(char *state_text)
{
char command[CMDSZ];
int state_number = -1;
if (GetCommandCode(command, sizeof(command), state_text, kOptionOff) >= 0) {
state_number = 0;
}
else if (GetCommandCode(command, sizeof(command), state_text, kOptionOn) >= 0) {
state_number = 1;
}
else if (GetCommandCode(command, sizeof(command), state_text, kOptionToggle) >= 0) {
state_number = 2;
}
else if (GetCommandCode(command, sizeof(command), state_text, kOptionBlink) >= 0) {
state_number = 3;
}
else if (GetCommandCode(command, sizeof(command), state_text, kOptionBlinkOff) >= 0) {
state_number = 4;
}
return state_number;
}
void SetSerialBaudrate(int baudrate)
{
Settings.baudrate = baudrate / 1200;
if (Serial.baudRate() != baudrate) {
if (seriallog_level) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate);
}
delay(100);
Serial.flush();
Serial.begin(baudrate, serial_config);
delay(10);
Serial.println();
}
}
void ClaimSerial(void)
{
serial_local = true;
AddLog_P(LOG_LEVEL_INFO, PSTR("SNS: Hardware Serial"));
SetSeriallog(LOG_LEVEL_NONE);
baudrate = Serial.baudRate();
Settings.baudrate = baudrate / 1200;
}
void SerialSendRaw(char *codes)
{
char *p;
char stemp[3];
uint8_t code;
int size = strlen(codes);
while (size > 0) {
strlcpy(stemp, codes, sizeof(stemp));
code = strtol(stemp, &p, 16);
Serial.write(code);
size -= 2;
codes += 2;
}
}
uint32_t GetHash(const char *buffer, size_t size)
{
uint32_t hash = 0;
for (uint16_t i = 0; i <= size; i++) {
hash += (uint8_t)*buffer++ * (i +1);
}
return hash;
}
void ShowSource(int source)
{
if ((source > 0) && (source < SRC_MAX)) {
char stemp1[20];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SRC: %s"), GetTextIndexed(stemp1, sizeof(stemp1), source, kCommandSource));
}
}
void WebHexCode(uint8_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
}
*/
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
}
uint32_t WebColor(uint8_t i)
{
uint32_t tcolor = (Settings.web_color[i][0] << 16) | (Settings.web_color[i][1] << 8) | Settings.web_color[i][2];
return tcolor;
}
/*********************************************************************************************\
* Response data handling
\*********************************************************************************************/
int Response_P(const char* format, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
va_list args;
va_start(args, format);
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), format, args);
va_end(args);
return len;
}
int ResponseAppend_P(const char* format, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
va_list args;
va_start(args, format);
int mlen = strlen(mqtt_data);
int len = vsnprintf_P(mqtt_data + mlen, sizeof(mqtt_data) - mlen, format, args);
va_end(args);
return len + mlen;
}
/*********************************************************************************************\
* GPIO Module and Template management
\*********************************************************************************************/
uint8_t ModuleNr()
{
// 0 = User module (255)
// 1 up = Template module 0 up
return (USER_MODULE == Settings.module) ? 0 : Settings.module +1;
}
bool ValidTemplateModule(uint8_t index)
{
for (uint8_t i = 0; i < sizeof(kModuleNiceList); i++) {
if (index == pgm_read_byte(kModuleNiceList + i)) {
return true;
}
}
return false;
}
bool ValidModule(uint8_t index)
{
if (index == USER_MODULE) { return true; }
return ValidTemplateModule(index);
}
String AnyModuleName(uint8_t index)
{
if (USER_MODULE == index) {
return String(Settings.user_template.name);
} else {
return FPSTR(kModules[index].name);
}
}
String ModuleName()
{
return AnyModuleName(Settings.module);
}
void ModuleGpios(myio *gp)
{
uint8_t *dest = (uint8_t *)gp;
memset(dest, GPIO_NONE, sizeof(myio));
uint8_t src[sizeof(mycfgio)];
if (USER_MODULE == Settings.module) {
memcpy(&src, &Settings.user_template.gp, sizeof(mycfgio));
} else {
memcpy_P(&src, &kModules[Settings.module].gp, sizeof(mycfgio));
}
// 11 85 00 85 85 00 00 00 15 38 85 00 00 81
// AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t *)&src, sizeof(mycfgio));
uint8_t j = 0;
for (uint8_t i = 0; i < sizeof(mycfgio); i++) {
if (6 == i) { j = 9; }
if (8 == i) { j = 12; }
dest[j] = src[i];
j++;
}
// 11 85 00 85 85 00 00 00 00 00 00 00 15 38 85 00 00 81
// AddLogBuffer(LOG_LEVEL_DEBUG, (uint8_t *)gp, sizeof(myio));
}
gpio_flag ModuleFlag()
{
gpio_flag flag;
if (USER_MODULE == Settings.module) {
flag = Settings.user_template.flag;
} else {
memcpy_P(&flag, &kModules[Settings.module].flag, sizeof(gpio_flag));
}
return flag;
}
void ModuleDefault(uint8_t module)
{
if (USER_MODULE == module) { module = WEMOS; } // Generic
Settings.user_template_base = module;
memcpy_P(&Settings.user_template, &kModules[module], sizeof(mytmplt));
}
void SetModuleType()
{
my_module_type = (USER_MODULE == Settings.module) ? Settings.user_template_base : Settings.module;
}
uint8_t ValidPin(uint8_t pin, uint8_t gpio)
{
uint8_t result = gpio;
if (((pin > 5) && (pin < 9)) || (11 == pin)) {
result = GPIO_NONE; // Disable flash pins GPIO6, GPIO7, GPIO8 and GPIO11
}
if ((WEMOS == Settings.module) && (!Settings.flag3.user_esp8285_enable)) {
if ((pin == 9) || (pin == 10)) { result = GPIO_NONE; } // Disable possible flash GPIO9 and GPIO10
}
return result;
}
bool ValidGPIO(uint8_t pin, uint8_t gpio)
{
return (GPIO_USER == ValidPin(pin, gpio)); // Only allow GPIO_USER pins
}
bool GetUsedInModule(uint8_t val, uint8_t *arr)
{
int offset = 0;
if (!val) { return false; } // None
if ((val >= GPIO_KEY1) && (val < GPIO_KEY1 + MAX_KEYS)) {
offset = (GPIO_KEY1_NP - GPIO_KEY1);
}
if ((val >= GPIO_KEY1_NP) && (val < GPIO_KEY1_NP + MAX_KEYS)) {
offset = -(GPIO_KEY1_NP - GPIO_KEY1);
}
if ((val >= GPIO_KEY1_INV) && (val < GPIO_KEY1_INV + MAX_KEYS)) {
offset = -(GPIO_KEY1_INV - GPIO_KEY1);
}
if ((val >= GPIO_KEY1_INV_NP) && (val < GPIO_KEY1_INV_NP + MAX_KEYS)) {
offset = -(GPIO_KEY1_INV_NP - GPIO_KEY1);
}
if ((val >= GPIO_SWT1) && (val < GPIO_SWT1 + MAX_SWITCHES)) {
offset = (GPIO_SWT1_NP - GPIO_SWT1);
}
if ((val >= GPIO_SWT1_NP) && (val < GPIO_SWT1_NP + MAX_SWITCHES)) {
offset = -(GPIO_SWT1_NP - GPIO_SWT1);
}
if ((val >= GPIO_REL1) && (val < GPIO_REL1 + MAX_RELAYS)) {
offset = (GPIO_REL1_INV - GPIO_REL1);
}
if ((val >= GPIO_REL1_INV) && (val < GPIO_REL1_INV + MAX_RELAYS)) {
offset = -(GPIO_REL1_INV - GPIO_REL1);
}
if ((val >= GPIO_LED1) && (val < GPIO_LED1 + MAX_LEDS)) {
offset = (GPIO_LED1_INV - GPIO_LED1);
}
if ((val >= GPIO_LED1_INV) && (val < GPIO_LED1_INV + MAX_LEDS)) {
offset = -(GPIO_LED1_INV - GPIO_LED1);
}
if ((val >= GPIO_PWM1) && (val < GPIO_PWM1 + MAX_PWMS)) {
offset = (GPIO_PWM1_INV - GPIO_PWM1);
}
if ((val >= GPIO_PWM1_INV) && (val < GPIO_PWM1_INV + MAX_PWMS)) {
offset = -(GPIO_PWM1_INV - GPIO_PWM1);
}
if ((val >= GPIO_CNTR1) && (val < GPIO_CNTR1 + MAX_COUNTERS)) {
offset = (GPIO_CNTR1_NP - GPIO_CNTR1);
}
if ((val >= GPIO_CNTR1_NP) && (val < GPIO_CNTR1_NP + MAX_COUNTERS)) {
offset = -(GPIO_CNTR1_NP - GPIO_CNTR1);
}
for (uint8_t i = 0; i < MAX_GPIO_PIN; i++) {
if (arr[i] == val) { return true; }
if (arr[i] == val + offset) { return true; }
}
return false;
}
bool JsonTemplate(const char* dataBuf)
{
// {"NAME":"Generic","GPIO":[17,254,29,254,7,254,254,254,138,254,139,254,254],"FLAG":1,"BASE":255}
if (strlen(dataBuf) < 9) { return false; } // Workaround exception if empty JSON like {} - Needs checks
StaticJsonBuffer<350> jb; // 331 from https://arduinojson.org/v5/assistant/
JsonObject& obj = jb.parseObject(dataBuf);
if (!obj.success()) { return false; }
// All parameters are optional allowing for partial changes
const char* name = obj[D_JSON_NAME];
if (name != nullptr) {
strlcpy(Settings.user_template.name, name, sizeof(Settings.user_template.name));
}
if (obj[D_JSON_GPIO].success()) {
for (uint8_t i = 0; i < sizeof(mycfgio); i++) {
Settings.user_template.gp.io[i] = obj[D_JSON_GPIO][i] | 0;
}
}
if (obj[D_JSON_FLAG].success()) {
uint8_t flag = obj[D_JSON_FLAG] | 0;
memcpy(&Settings.user_template.flag, &flag, sizeof(gpio_flag));
}
if (obj[D_JSON_BASE].success()) {
uint8_t base = obj[D_JSON_BASE];
if ((0 == base) || !ValidTemplateModule(base -1)) { base = 18; }
Settings.user_template_base = base -1; // Default WEMOS
}
return true;
}
void TemplateJson()
{
Response_P(PSTR("{\"" D_JSON_NAME "\":\"%s\",\"" D_JSON_GPIO "\":["), Settings.user_template.name);
for (uint8_t i = 0; i < sizeof(Settings.user_template.gp); i++) {
ResponseAppend_P(PSTR("%s%d"), (i>0)?",":"", Settings.user_template.gp.io[i]);
}
ResponseAppend_P(PSTR("],\"" D_JSON_FLAG "\":%d,\"" D_JSON_BASE "\":%d}"), Settings.user_template.flag, Settings.user_template_base +1);
}
/*********************************************************************************************\
* Sleep aware time scheduler functions borrowed from ESPEasy
\*********************************************************************************************/
long TimeDifference(unsigned long prev, unsigned long next)
{
// Return the time difference as a signed value, taking into account the timers may overflow.
// Returned timediff is between -24.9 days and +24.9 days.
// Returned value is positive when "next" is after "prev"
long signed_diff = 0;
// To cast a value to a signed long, the difference may not exceed half 0xffffffffUL (= 4294967294)
const unsigned long half_max_unsigned_long = 2147483647u; // = 2^31 -1
if (next >= prev) {
const unsigned long diff = next - prev;
if (diff <= half_max_unsigned_long) { // Normal situation, just return the difference.
signed_diff = static_cast<long>(diff); // Difference is a positive value.
} else {
// prev has overflow, return a negative difference value
signed_diff = static_cast<long>((0xffffffffUL - next) + prev + 1u);
signed_diff = -1 * signed_diff;
}
} else {
// next < prev
const unsigned long diff = prev - next;
if (diff <= half_max_unsigned_long) { // Normal situation, return a negative difference value
signed_diff = static_cast<long>(diff);
signed_diff = -1 * signed_diff;
} else {
// next has overflow, return a positive difference value
signed_diff = static_cast<long>((0xffffffffUL - prev) + next + 1u);
}
}
return signed_diff;
}
long TimePassedSince(unsigned long 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(unsigned long timer)
{
// Check if a certain timeout has been reached.
const long passed = TimePassedSince(timer);
return (passed >= 0);
}
void SetNextTimeInterval(unsigned long& timer, const unsigned long 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);
}
/*********************************************************************************************\
* Basic I2C routines
\*********************************************************************************************/
#ifdef USE_I2C
const uint8_t I2C_RETRY_COUNTER = 3;
uint32_t i2c_buffer = 0;
bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size)
{
uint8_t x = I2C_RETRY_COUNTER;
i2c_buffer = 0;
do {
Wire.beginTransmission(addr); // start transmission to device
Wire.write(reg); // sends register address to read from
if (0 == Wire.endTransmission(false)) { // Try to become I2C Master, send data and collect bytes, keep master status for next request...
Wire.requestFrom((int)addr, (int)size); // send data n-bytes read
if (Wire.available() == size) {
for (uint8_t i = 0; i < size; i++) {
i2c_buffer = i2c_buffer << 8 | Wire.read(); // receive DATA
}
}
}
x--;
} while (Wire.endTransmission(true) != 0 && x != 0); // end transmission
return (x);
}
bool I2cValidRead8(uint8_t *data, uint8_t addr, uint8_t reg)
{
bool status = I2cValidRead(addr, reg, 1);
*data = (uint8_t)i2c_buffer;
return status;
}
bool I2cValidRead16(uint16_t *data, uint8_t addr, uint8_t reg)
{
bool status = I2cValidRead(addr, reg, 2);
*data = (uint16_t)i2c_buffer;
return status;
}
bool I2cValidReadS16(int16_t *data, uint8_t addr, uint8_t reg)
{
bool status = I2cValidRead(addr, reg, 2);
*data = (int16_t)i2c_buffer;
return status;
}
bool I2cValidRead16LE(uint16_t *data, uint8_t addr, uint8_t reg)
{
uint16_t ldata;
bool status = I2cValidRead16(&ldata, addr, reg);
*data = (ldata >> 8) | (ldata << 8);
return status;
}
bool I2cValidReadS16_LE(int16_t *data, uint8_t addr, uint8_t reg)
{
uint16_t ldata;
bool status = I2cValidRead16LE(&ldata, addr, reg);
*data = (int16_t)ldata;
return status;
}
bool I2cValidRead24(int32_t *data, uint8_t addr, uint8_t reg)
{
bool status = I2cValidRead(addr, reg, 3);
*data = i2c_buffer;
return status;
}
uint8_t I2cRead8(uint8_t addr, uint8_t reg)
{
I2cValidRead(addr, reg, 1);
return (uint8_t)i2c_buffer;
}
uint16_t I2cRead16(uint8_t addr, uint8_t reg)
{
I2cValidRead(addr, reg, 2);
return (uint16_t)i2c_buffer;
}
int16_t I2cReadS16(uint8_t addr, uint8_t reg)
{
I2cValidRead(addr, reg, 2);
return (int16_t)i2c_buffer;
}
uint16_t I2cRead16LE(uint8_t addr, uint8_t reg)
{
I2cValidRead(addr, reg, 2);
uint16_t temp = (uint16_t)i2c_buffer;
return (temp >> 8) | (temp << 8);
}
int16_t I2cReadS16_LE(uint8_t addr, uint8_t reg)
{
return (int16_t)I2cRead16LE(addr, reg);
}
int32_t I2cRead24(uint8_t addr, uint8_t reg)
{
I2cValidRead(addr, reg, 3);
return i2c_buffer;
}
bool I2cWrite(uint8_t addr, uint8_t reg, uint32_t val, uint8_t size)
{
uint8_t x = I2C_RETRY_COUNTER;
do {
Wire.beginTransmission((uint8_t)addr); // start transmission to device
Wire.write(reg); // sends register address to write to
uint8_t bytes = size;
while (bytes--) {
Wire.write((val >> (8 * bytes)) & 0xFF); // write data
}
x--;
} while (Wire.endTransmission(true) != 0 && x != 0); // end transmission
return (x);
}
bool I2cWrite8(uint8_t addr, uint8_t reg, uint16_t val)
{
return I2cWrite(addr, reg, val, 1);
}
bool I2cWrite16(uint8_t addr, uint8_t reg, uint16_t val)
{
return I2cWrite(addr, reg, val, 2);
}
int8_t I2cReadBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len)
{
Wire.beginTransmission((uint8_t)addr);
Wire.write((uint8_t)reg);
Wire.endTransmission();
if (len != Wire.requestFrom((uint8_t)addr, (uint8_t)len)) {
return 1;
}
while (len--) {
*reg_data = (uint8_t)Wire.read();
reg_data++;
}
return 0;
}
int8_t I2cWriteBuffer(uint8_t addr, uint8_t reg, uint8_t *reg_data, uint16_t len)
{
Wire.beginTransmission((uint8_t)addr);
Wire.write((uint8_t)reg);
while (len--) {
Wire.write(*reg_data);
reg_data++;
}
Wire.endTransmission();
return 0;
}
void I2cScan(char *devs, unsigned int devs_len)
{
// Return error codes defined in twi.h and core_esp8266_si2c.c
// I2C_OK 0
// I2C_SCL_HELD_LOW 1 = SCL held low by another device, no procedure available to recover
// I2C_SCL_HELD_LOW_AFTER_READ 2 = I2C bus error. SCL held low beyond slave clock stretch time
// I2C_SDA_HELD_LOW 3 = I2C bus error. SDA line held low by slave/another_master after n bits
// I2C_SDA_HELD_LOW_AFTER_INIT 4 = line busy. SDA again held low by another device. 2nd master?
uint8_t error = 0;
uint8_t address = 0;
uint8_t any = 0;
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_DEVICES_FOUND_AT));
for (address = 1; address <= 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (0 == error) {
any = 1;
snprintf_P(devs, devs_len, PSTR("%s 0x%02x"), devs, address);
}
else if (error != 2) { // Seems to happen anyway using this scan
any = 2;
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"Error %d at 0x%02x"), error, address);
break;
}
}
if (any) {
strncat(devs, "\"}", devs_len - strlen(devs) -1);
}
else {
snprintf_P(devs, devs_len, PSTR("{\"" D_CMND_I2CSCAN "\":\"" D_JSON_I2CSCAN_NO_DEVICES_FOUND "\"}"));
}
}
bool I2cDevice(uint8_t addr)
{
for (uint8_t address = 1; address <= 127; address++) {
Wire.beginTransmission(address);
if (!Wire.endTransmission() && (address == addr)) {
return true;
}
}
return false;
}
#endif // USE_I2C
/*********************************************************************************************\
* Syslog
*
* Example:
* AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_LOG "Any value %d"), value);
*
\*********************************************************************************************/
void SetSeriallog(uint8_t loglevel)
{
Settings.seriallog_level = loglevel;
seriallog_level = loglevel;
seriallog_timer = 0;
}
#ifdef USE_WEBSERVER
void GetLog(uint8_t idx, char** entry_pp, size_t* len_p)
{
char* entry_p = nullptr;
size_t len = 0;
if (idx) {
char* it = web_log;
do {
uint8_t cur_idx = *it;
it++;
size_t tmp = strchrspn(it, '\1');
tmp++; // Skip terminating '\1'
if (cur_idx == idx) { // Found the requested entry
len = tmp;
entry_p = it;
break;
}
it += tmp;
} while (it < web_log + WEB_LOG_SIZE && *it != '\0');
}
*entry_pp = entry_p;
*len_p = len;
}
#endif // USE_WEBSERVER
void Syslog(void)
{
// Destroys log_data
char syslog_preamble[64]; // Hostname + Id
uint32_t current_hash = GetHash(Settings.syslog_host, strlen(Settings.syslog_host));
if (syslog_host_hash != current_hash) {
syslog_host_hash = current_hash;
WiFi.hostByName(Settings.syslog_host, syslog_host_addr); // If sleep enabled this might result in exception so try to do it once using hash
}
if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) {
snprintf_P(syslog_preamble, sizeof(syslog_preamble), PSTR("%s ESP-"), my_hostname);
memmove(log_data + strlen(syslog_preamble), log_data, sizeof(log_data) - strlen(syslog_preamble));
log_data[sizeof(log_data) -1] = '\0';
memcpy(log_data, syslog_preamble, strlen(syslog_preamble));
PortUdp.write(log_data);
PortUdp.endPacket();
delay(1); // Add time for UDP handling (#5512)
} else {
syslog_level = 0;
syslog_timer = SYSLOG_TIMER;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_HOST_NOT_FOUND ". " D_RETRY_IN " %d " D_UNIT_SECOND), SYSLOG_TIMER);
}
}
void AddLog(uint8_t loglevel)
{
char mxtime[10]; // "13:45:21 "
snprintf_P(mxtime, sizeof(mxtime), PSTR("%02d" D_HOUR_MINUTE_SEPARATOR "%02d" D_MINUTE_SECOND_SEPARATOR "%02d "), RtcTime.hour, RtcTime.minute, RtcTime.second);
if (loglevel <= seriallog_level) {
Serial.printf("%s%s\r\n", mxtime, log_data);
}
#ifdef USE_WEBSERVER
if (Settings.webserver && (loglevel <= Settings.weblog_level)) {
// Delimited, zero-terminated buffer of log lines.
// Each entry has this format: [index][log data]['\1']
if (!web_log_index) web_log_index++; // Index 0 is not allowed as it is the end of char string
while (web_log_index == web_log[0] || // If log already holds the next index, remove it
strlen(web_log) + strlen(log_data) + 13 > WEB_LOG_SIZE) // 13 = web_log_index + mxtime + '\1' + '\0'
{
char* it = web_log;
it++; // Skip web_log_index
it += strchrspn(it, '\1'); // Skip log line
it++; // Skip delimiting "\1"
memmove(web_log, it, WEB_LOG_SIZE -(it-web_log)); // Move buffer forward to remove oldest log line
}
snprintf_P(web_log, sizeof(web_log), PSTR("%s%c%s%s\1"), web_log, web_log_index++, mxtime, log_data);
if (!web_log_index) web_log_index++; // Index 0 is not allowed as it is the end of char string
}
#endif // USE_WEBSERVER
if (!global_state.wifi_down && (loglevel <= syslog_level)) { Syslog(); }
}
void AddLog_P(uint8_t loglevel, const char *formatP)
{
snprintf_P(log_data, sizeof(log_data), formatP);
AddLog(loglevel);
}
void AddLog_P(uint8_t loglevel, const char *formatP, const char *formatP2)
{
char message[100];
snprintf_P(log_data, sizeof(log_data), formatP);
snprintf_P(message, sizeof(message), formatP2);
strncat(log_data, message, sizeof(log_data) - strlen(log_data) -1);
AddLog(loglevel);
}
void AddLog_P2(uint8_t loglevel, PGM_P formatP, ...)
{
va_list arg;
va_start(arg, formatP);
vsnprintf_P(log_data, sizeof(log_data), formatP, arg);
va_end(arg);
AddLog(loglevel);
}
void AddLogBuffer(uint8_t loglevel, uint8_t *buffer, int count)
{
snprintf_P(log_data, sizeof(log_data), PSTR("DMP:"));
for (int i = 0; i < count; i++) {
snprintf_P(log_data, sizeof(log_data), PSTR("%s %02X"), log_data, *(buffer++));
}
AddLog(loglevel);
}
void AddLogSerial(uint8_t loglevel)
{
AddLogBuffer(loglevel, (uint8_t*)serial_in_buffer, serial_in_byte_counter);
}
void AddLogMissed(char *sensor, uint8_t misses)
{
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SNS: %s missed %d"), sensor, SENSOR_MAX_MISS - misses);
}