#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()) { if (elem < 0) return false; //ignore e.g. {"ps":-1} *val = elem; return true; } else if (elem.is()) { 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> 8 ^ *data_p++; x ^= x>>4; crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x); } return crc; }