17be0a2c12
Soft watchdog timer (by poelzi)
347 lines
9.6 KiB
C++
347 lines
9.6 KiB
C++
#include "wled.h"
|
|
#include "fcn_declare.h"
|
|
#include "const.h"
|
|
|
|
|
|
//helper to get int value at a position in string
|
|
int getNumVal(const String* req, uint16_t pos)
|
|
{
|
|
return req->substring(pos+3).toInt();
|
|
}
|
|
|
|
|
|
//helper to get int value with in/decrementing support via ~ syntax
|
|
void parseNumber(const char* str, byte* val, byte minv, byte maxv)
|
|
{
|
|
if (str == nullptr || str[0] == '\0') return;
|
|
if (str[0] == 'r') {*val = random8(minv,maxv); return;}
|
|
bool wrap = false;
|
|
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
|
|
if (str[0] == '~') {
|
|
int out = atoi(str +1);
|
|
if (out == 0)
|
|
{
|
|
if (str[1] == '0') return;
|
|
if (str[1] == '-')
|
|
{
|
|
*val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around
|
|
} else {
|
|
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
|
|
}
|
|
} else {
|
|
if (wrap && *val == maxv && out > 0) out = minv;
|
|
else if (wrap && *val == minv && out < 0) out = maxv;
|
|
else {
|
|
out += *val;
|
|
if (out > maxv) out = maxv;
|
|
if (out < minv) out = minv;
|
|
}
|
|
*val = out;
|
|
}
|
|
} else
|
|
{
|
|
byte p1 = atoi(str);
|
|
const char* str2 = strchr(str,'~'); //min/max range (for preset cycle, e.g. "1~5~")
|
|
if (str2) {
|
|
byte p2 = atoi(str2+1);
|
|
presetCycMin = p1; presetCycMax = p2;
|
|
while (isdigit((str2+1)[0])) str2++;
|
|
parseNumber(str2+1, val, p1, p2);
|
|
} else {
|
|
*val = p1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
|
|
if (elem.is<int>()) {
|
|
if (elem < 0) return false; //ignore e.g. {"ps":-1}
|
|
*val = elem;
|
|
return true;
|
|
} else if (elem.is<const char*>()) {
|
|
const char* str = elem;
|
|
size_t len = strnlen(str, 12);
|
|
if (len == 0 || len > 10) return false;
|
|
parseNumber(str, val, vmin, vmax);
|
|
return true;
|
|
}
|
|
return false; //key does not exist
|
|
}
|
|
|
|
|
|
bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv)
|
|
{
|
|
int pos = req->indexOf(key);
|
|
if (pos < 1) return false;
|
|
if (req->length() < (unsigned int)(pos + 4)) return false;
|
|
parseNumber(req->c_str() + pos +3, val, minv, maxv);
|
|
return true;
|
|
}
|
|
|
|
|
|
//append a numeric setting to string buffer
|
|
void sappend(char stype, const char* key, int val)
|
|
{
|
|
char ds[] = "d.Sf.";
|
|
|
|
switch(stype)
|
|
{
|
|
case 'c': //checkbox
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(".checked=");
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
case 'v': //numeric
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(".value=");
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
case 'i': //selectedIndex
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(SET_F(".selectedIndex="));
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//append a string setting to buffer
|
|
void sappends(char stype, const char* key, char* val)
|
|
{
|
|
switch(stype)
|
|
{
|
|
case 's': {//string (we can interpret val as char*)
|
|
String buf = val;
|
|
//convert "%" to "%%" to make EspAsyncWebServer happy
|
|
//buf.replace("%","%%");
|
|
oappend("d.Sf.");
|
|
oappend(key);
|
|
oappend(".value=\"");
|
|
oappend(buf.c_str());
|
|
oappend("\";");
|
|
break;}
|
|
case 'm': //message
|
|
oappend(SET_F("d.getElementsByClassName"));
|
|
oappend(key);
|
|
oappend(SET_F(".innerHTML=\""));
|
|
oappend(val);
|
|
oappend("\";");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool oappendi(int i)
|
|
{
|
|
char s[11];
|
|
sprintf(s, "%d", i);
|
|
return oappend(s);
|
|
}
|
|
|
|
|
|
bool oappend(const char* txt)
|
|
{
|
|
uint16_t len = strlen(txt);
|
|
if (olen + len >= SETTINGS_STACK_BUF_SIZE)
|
|
return false; // buffer full
|
|
strcpy(obuf + olen, txt);
|
|
olen += len;
|
|
return true;
|
|
}
|
|
|
|
|
|
void prepareHostname(char* hostname)
|
|
{
|
|
const char *pC = serverDescription;
|
|
uint8_t pos = 5;
|
|
|
|
while (*pC && pos < 24) { // while !null and not over length
|
|
if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
|
|
hostname[pos] = *pC;
|
|
pos++;
|
|
} else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
|
|
hostname[pos] = '-';
|
|
pos++;
|
|
}
|
|
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
|
|
pC++;
|
|
}
|
|
// if the hostname is left blank, use the mac address/default mdns name
|
|
if (pos < 6) {
|
|
sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6);
|
|
} else { //last character must not be hyphen
|
|
while (pos > 0 && hostname[pos -1] == '-') {
|
|
hostname[pos -1] = 0;
|
|
pos--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool isAsterisksOnly(const char* str, byte maxLen)
|
|
{
|
|
for (byte i = 0; i < maxLen; i++) {
|
|
if (str[i] == 0) break;
|
|
if (str[i] != '*') return false;
|
|
}
|
|
//at this point the password contains asterisks only
|
|
return (str[0] != 0); //false on empty string
|
|
}
|
|
|
|
|
|
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
|
|
bool requestJSONBufferLock(uint8_t module)
|
|
{
|
|
unsigned long now = millis();
|
|
|
|
while (jsonBufferLock && millis()-now < 1000) delay(1); // wait for a second for buffer lock
|
|
|
|
if (millis()-now >= 1000) {
|
|
DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
return false; // waiting time-outed
|
|
}
|
|
|
|
jsonBufferLock = module ? module : 255;
|
|
DEBUG_PRINT(F("JSON buffer locked. ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
fileDoc = &doc; // used for applying presets (presets.cpp)
|
|
doc.clear();
|
|
return true;
|
|
}
|
|
|
|
|
|
void releaseJSONBufferLock()
|
|
{
|
|
DEBUG_PRINT(F("JSON buffer released. ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
fileDoc = nullptr;
|
|
jsonBufferLock = 0;
|
|
}
|
|
|
|
|
|
// extracts effect mode (or palette) name from names serialized string
|
|
// caller must provide large enough buffer for name (incluing SR extensions)!
|
|
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
|
|
{
|
|
if (src == JSON_mode_names || src == nullptr) {
|
|
if (mode < MODE_COUNT) {
|
|
char lineBuffer[256];
|
|
//strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode])));
|
|
strcpy_P(lineBuffer, WS2812FX::_modeData[mode]);
|
|
if (strlen(lineBuffer) > 0) {
|
|
size_t j = 0;
|
|
for (; j < maxLen; j++) {
|
|
if (lineBuffer[j] == '\0' || lineBuffer[j] == '@') break;
|
|
dest[j] = lineBuffer[j];
|
|
}
|
|
dest[j] = 0; // terminate string
|
|
}
|
|
return strlen(dest);
|
|
} else return 0;
|
|
}
|
|
|
|
uint8_t qComma = 0;
|
|
bool insideQuotes = false;
|
|
uint8_t printedChars = 0;
|
|
char singleJsonSymbol;
|
|
size_t len = strlen_P(src);
|
|
|
|
// Find the mode name in JSON
|
|
for (size_t i = 0; i < len; i++) {
|
|
singleJsonSymbol = pgm_read_byte_near(src + i);
|
|
if (singleJsonSymbol == '\0') break;
|
|
if (singleJsonSymbol == '@' && insideQuotes && qComma == mode) break; //stop when SR extension encountered
|
|
switch (singleJsonSymbol) {
|
|
case '"':
|
|
insideQuotes = !insideQuotes;
|
|
break;
|
|
case '[':
|
|
case ']':
|
|
break;
|
|
case ',':
|
|
if (!insideQuotes) qComma++;
|
|
default:
|
|
if (!insideQuotes || (qComma != mode)) break;
|
|
dest[printedChars++] = singleJsonSymbol;
|
|
}
|
|
if ((qComma > mode) || (printedChars >= maxLen)) break;
|
|
}
|
|
dest[printedChars] = '\0';
|
|
return strlen(dest);
|
|
}
|
|
|
|
|
|
// extracts effect slider data (1st group after @)
|
|
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen)
|
|
{
|
|
dest[0] = '\0'; // start by clearing buffer
|
|
|
|
if (mode < MODE_COUNT) {
|
|
String lineBuffer = WS2812FX::_modeData[mode];
|
|
if (lineBuffer.length() > 0) {
|
|
int16_t start = lineBuffer.indexOf('@');
|
|
int16_t stop = lineBuffer.indexOf(';', start);
|
|
if (start>0 && stop>0) {
|
|
String names = lineBuffer.substring(start+1, stop);
|
|
int16_t nameBegin = 0, nameEnd;
|
|
for (size_t i=0; i<=slider; i++) {
|
|
const char *tmpstr;
|
|
dest[0] = '\0'; //clear dest buffer
|
|
if (i > 0 && nameBegin == 0) break; // there are no more names
|
|
nameEnd = names.indexOf(',', nameBegin);
|
|
if (names.charAt(nameBegin) == '!') {
|
|
switch (i) {
|
|
case 0: tmpstr = PSTR("FX Speed"); break;
|
|
case 1: tmpstr = PSTR("FX Intensity"); break;
|
|
case 2: tmpstr = PSTR("FX Custom 1"); break;
|
|
case 3: tmpstr = PSTR("FX Custom 2"); break;
|
|
case 4: tmpstr = PSTR("FX Custom 3"); break;
|
|
default: tmpstr = PSTR("FX Custom"); break;
|
|
}
|
|
} else {
|
|
if (nameEnd<0) tmpstr = names.substring(nameBegin).c_str(); // did not find ",", last name?
|
|
else tmpstr = names.substring(nameBegin, nameEnd).c_str();
|
|
}
|
|
strncpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
|
|
nameBegin = nameEnd+1; // next name (if "," is not found it will be 0)
|
|
} // next slider
|
|
|
|
// we have slider name (including default value) in the dest buffer
|
|
for (size_t i=0; i<strlen(dest); i++) if (dest[i]=='=') { dest[i]='\0'; break; } // truncate default value
|
|
|
|
} else {
|
|
// defaults to just speed and intensity since there is no slider data
|
|
switch (slider) {
|
|
case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
|
|
case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
|
|
}
|
|
}
|
|
}
|
|
return strlen(dest);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
uint16_t crc16(const unsigned char* data_p, size_t length) {
|
|
uint8_t x;
|
|
uint16_t crc = 0xFFFF;
|
|
if (!length) return 0x1D0F;
|
|
while (length--) {
|
|
x = crc >> 8 ^ *data_p++;
|
|
x ^= x>>4;
|
|
crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
|
|
}
|
|
return crc;
|
|
} |