diff --git a/.travis.yml b/.travis.yml index e46add827..0b0926b69 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,7 +69,7 @@ script: - PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_ESP32-wrover-kit_4M316k - PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_ESP32_4M316k - PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_ESP8266_4M1M_VCC - - PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_ESP8266_4M1M_VCC_MDNS_SD + #- PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_ESP8266_4M1M_VCC_MDNS_SD #- PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_beta_ESP8266_16M - PLATFORMIO_BUILD_FLAGS="-D CONTINUOUS_INTEGRATION" platformio run -e test_beta_ESP8266_4M1M diff --git a/dist/Release_notes.txt b/dist/Release_notes.txt index e2cf7894d..ab33bceb6 100644 --- a/dist/Release_notes.txt +++ b/dist/Release_notes.txt @@ -1,3 +1,126 @@ +------------------------------------------------- +Changes in release mega-20200328 (since mega-20200310) +------------------------------------------------- + +Release date: za 28 mrt 2020 2:55:55 CET + +Bartlomiej Zimon (2): + [SysVars] small optimization + [SysVars] concat->String() + +Boris Krivonog (30): + Initial commit of the Mitsibutshi HP integration. + Added device values. + first contact + push on change + push on change + unified code read and update + added update ... + set temperature ... + write all parameters + logging ... + logs fixed + if update was successful, update settings immediatelly + renamed to settings + don't read during write, don't request info we don't care about ... + Added info about which specific commit used. + reverted changes made in other files + reverted changes made in other files + reverted changes made in other files + fixed compile time warnings + Renamed to 092 plugin. + Rewrote communication part, removed delays from code, optimised handling and storing of data ... + Write re-enabled + Renamed to 093 plugin. + Renamed to 093 plugin. + Improved write operations + Fixed build when debug is disabled + Make sure to break out of switch-case. + Handle only "MHP" commands + adding documentation ... + adding documentation ... + +Gijs Noorlander (47): + [P091] Move P165 SerSwitch from playground to main repository + [P091] Add documentation + [P091 SerSwitch] Update to use latest sources of enesbcs + Minify the JS code + store in flash + [Docs] Add stub for documentation of P093 - Mitsubishi Heat Pump + [SC16IS752] Add Library + [SC16IS752 lib] Update manifest to register as PIO library + [ESPEasySerial] Update manifest to mark SC16IS752 dependency + [SC16IS752] Use I2C-UART bridge in ESPEasySerial (ESP8266) + [SC16IS752] Extend Serial Helper to support I2C UART + Fix warnings in ccronexpr test code + [Serial] Mark plugins using serial as device type serial + [I2C serial] Disable debugging + [Docs] Add port type to plugin substitutions + [Docs] Mark all plugins using Serial as port type + [I2C UART] Speed up reading from I2C Serial + [I2C UART] Cache FIFO available to speed up reading + [I2C UART] Limit calls to serial.available() + [code analytics] Use PVS studio + [ESP32] Add SDS011 support for ESP32 + [Task] Start scheduled runs again after (re)enableTask command + [I2C UART] Mark ESPEasySerial library as v2.0.0 + [ESP32] Make sure to include the version tag in ESPEasySerial libdeb + [I2C] Add I2C frequency setting + ESPEasy Status LED is not working on ESP32 with buildin LED (#2940) + Pulse Counter must retain its values after reboot (#2268) + [Build] Disable test_ESP8266_4M1M_VCC_MDNS_SD due to max sketch size + [JSON] Fix JSON output of values for single task + [SerialProxy] Add regexp filtering + [Lib] Add RegExp lib + [Serial Proxy] Add regexp filtering using captures + [Serial Proxy] Add extra filter parameters + [Serial Proxy] Move plugin specific struct to .h/.cpp to support captures + [Serial Proxy] Use capture filtering + [Web] Make command field on Tools page much wider + [Serial Proxy] Only check on valid filter rules + [SysVar] Add extra system variables to get build info + [Serial Proxy] Fix check in empty string test + Add generic hexToUL functions + [WiFi] Start webserver before starting wifi (long delay to receive 1st byte) + [Serial Log] Keep a bit more free memory when processing serial log + [Serial Proxy] Don't process on empty regexp + Check if plugin uses formula before trying to process formula + [ESP32] Fix crash in startup connecting to WiFi (#2960 ) + [SGP30] Fix output all values to controller + [PIO] feature/stage no longer available for building latest core libs + [PIO] Use platform_packages for all core libs supporting those + +Grovkillen (8): + [rules] commented out old upload html + [rules] forms uncommented (needed for rules set) + [rules] removed the "onSubmit" event + [rules] narrowed scope of form (to not trigger onChange if btn clicked) + [rules] file upload instead of form upload + [rules] removed commented code + [rules] size is updated when saved + [rules] td-er talked me into it... thanks mate! + +Michael Schröder (1): + Fix double-Ok at command output + +TD-er (4): + Start of implementing SC16IS752 support + Fix resetpulsecounter for P003_Pulse (#2710) + [Blynk] Fix building C015 Blynk controller + Fix ArduinoIDE Build error due to missing include + +borisk (6): + Updating documentation + Updating documentation + Updating documentation + Add as part of testing plugins ... + Restructured docs a bit so its easier to follow + updated docs + +tonhuisman (2): + [P023] Fix for text overflowing onto next line(s) + Added condition for 64x48 and rotated combined + + ------------------------------------------------- Changes in release mega-20200310 (since mega-20200305) ------------------------------------------------- diff --git a/lib/Regexp/README.md b/lib/Regexp/README.md new file mode 100644 index 000000000..0f5551a3f --- /dev/null +++ b/lib/Regexp/README.md @@ -0,0 +1,14 @@ +Regexp +====== + +Regular expression parser for microcontrollers based on the Lua one. + +Documentation on interfacing with the library, and other details at: + +http://www.gammon.com.au/forum/?id=11063 + +## Documentation on regular expressions (Lua patterns) + +* [Official Lua documentation](http://www.lua.org/manual/5.2/manual.html#6.4.1) + +* [Simplified documentation from MUSHclient help](http://www.gammon.com.au/scripts/doc.php?lua=string.find) diff --git a/lib/Regexp/Regexp.h b/lib/Regexp/Regexp.h new file mode 100644 index 000000000..7c462a503 --- /dev/null +++ b/lib/Regexp/Regexp.h @@ -0,0 +1 @@ +#include "src/Regexp.h" diff --git a/lib/Regexp/examples/GlobalMatch/GlobalMatch.pde b/lib/Regexp/examples/GlobalMatch/GlobalMatch.pde new file mode 100644 index 000000000..b252aa468 --- /dev/null +++ b/lib/Regexp/examples/GlobalMatch/GlobalMatch.pde @@ -0,0 +1,52 @@ +#include + +// called for each match +void match_callback (const char * match, // matching string (not null-terminated) + const unsigned int length, // length of matching string + const MatchState & ms) // MatchState in use (to get captures) +{ +char cap [10]; // must be large enough to hold captures + + Serial.print ("Matched: "); + Serial.write ((byte *) match, length); + Serial.println (); + + for (byte i = 0; i < ms.level; i++) + { + Serial.print ("Capture "); + Serial.print (i, DEC); + Serial.print (" = "); + ms.GetCapture (cap, i); + Serial.println (cap); + } // end of for each capture + +} // end of match_callback + + +void setup () +{ + Serial.begin (115200); + Serial.println (); + unsigned long count; + + // what we are searching (the target) + char buf [100] = "The quick brown fox jumps over the lazy wolf"; + + // match state object + MatchState ms (buf); + + // original buffer + Serial.println (buf); + + // search for three letters followed by a space (two captures) + count = ms.GlobalMatch ("(%a+)( )", match_callback); + + // show results + Serial.print ("Found "); + Serial.print (count); // 8 in this case + Serial.println (" matches."); + + +} // end of setup + +void loop () {} \ No newline at end of file diff --git a/lib/Regexp/examples/GlobalReplace/GlobalReplace.pde b/lib/Regexp/examples/GlobalReplace/GlobalReplace.pde new file mode 100644 index 000000000..179b1d94f --- /dev/null +++ b/lib/Regexp/examples/GlobalReplace/GlobalReplace.pde @@ -0,0 +1,61 @@ +#include + +// called for every match +void replace_callback (const char * match, // what we found + const unsigned int length, // how long it was + const char * & replacement, // put replacement here + unsigned int & replacement_length, // put replacement length here + const MatchState & ms) // for looking up captures +{ + + // show matching text + Serial.print("Match = "); + Serial.write((byte *) match, length); + Serial.println (); + + replacement = "Nick"; + replacement_length = 4; +} // end of replace_callback + +void setup () +{ + Serial.begin (115200); + Serial.println (); + unsigned long count; + + // what we are searching (the target) + char buf [100] = "The quick brown fox jumps over the lazy wolf"; + + // match state object + MatchState ms (buf); + + // original buffer + Serial.println (buf); + + // search for three letters + count = ms.GlobalReplace ("%a+", replace_callback); + + // show results + Serial.print ("Converted string: "); + Serial.println (buf); + Serial.print ("Found "); + Serial.print (count); // 9 in this case + Serial.println (" matches."); + + // copy in new target + strcpy (buf, "But does it get goat's blood out?"); + ms.Target (buf); // recompute length + + // replace vowels with * + count = ms.GlobalReplace ("[aeiou]", "*"); + + // show results + Serial.print ("Converted string: "); + Serial.println (buf); + Serial.print ("Found "); + Serial.print (count); // 13 in this case + Serial.println (" matches."); + +} // end of setup + +void loop () {} diff --git a/lib/Regexp/examples/GlobalReplace_Entities/GlobalReplace_Entities.pde b/lib/Regexp/examples/GlobalReplace_Entities/GlobalReplace_Entities.pde new file mode 100644 index 000000000..0122dcc62 --- /dev/null +++ b/lib/Regexp/examples/GlobalReplace_Entities/GlobalReplace_Entities.pde @@ -0,0 +1,45 @@ +#include + +// called for every match +void replace_callback (const char * match, // what we found + const unsigned int length, // how long it was + const char * & replacement, // put replacement here + unsigned int & replacement_length, // put replacement length here + const MatchState & ms) // for looking up captures +{ +static byte c; // for holding replacement byte, must be static + + char hexdigits [3]; // to hold hex string + + // get first capture + ms.GetCapture (hexdigits, 0); + // convert from hex to printable + c = strtol (hexdigits, NULL, 16); + + // set as replacement + replacement = (char *) &c; + replacement_length = 1; +} // end of replace_callback + + +void setup () +{ + Serial.begin (115200); + + // what we are searching + char buf [100] = "%7B%22John+Doe%22%7D"; + + // for matching regular expressions + MatchState ms (buf); + + // easy part, replace + by space + ms.GlobalReplace ("%+", " "); + + // replace %xx (eg. %22) by what the hex code represents + ms.GlobalReplace ("%%(%x%x)", replace_callback); + + Serial.println (buf); + +} // end of setup + +void loop () {} diff --git a/lib/Regexp/examples/Match/Match.pde b/lib/Regexp/examples/Match/Match.pde new file mode 100644 index 000000000..0ede8976c --- /dev/null +++ b/lib/Regexp/examples/Match/Match.pde @@ -0,0 +1,29 @@ +#include + +void setup () +{ + Serial.begin (115200); + + // match state object + MatchState ms; + + // what we are searching (the target) + char buf [100] = "The quick brown fox jumps over the lazy wolf"; + ms.Target (buf); // set its address + Serial.println (buf); + + char result = ms.Match ("f.x"); + + if (result > 0) + { + Serial.print ("Found match at: "); + Serial.println (ms.MatchStart); // 16 in this case + Serial.print ("Match length: "); + Serial.println (ms.MatchLength); // 3 in this case + } + else + Serial.println ("No match."); + +} // end of setup + +void loop () {} diff --git a/lib/Regexp/examples/MatchCount/MatchCount.pde b/lib/Regexp/examples/MatchCount/MatchCount.pde new file mode 100644 index 000000000..553a2ee72 --- /dev/null +++ b/lib/Regexp/examples/MatchCount/MatchCount.pde @@ -0,0 +1,23 @@ +#include + +void setup () +{ + Serial.begin (115200); + + // match state object + MatchState ms; + + // what we are searching (the target) + char buf [100] = "The quick brown fox jumps over the lazy wolf"; + ms.Target (buf); // set its address + + unsigned int count = ms.MatchCount ("[aeiou]"); + + Serial.println (buf); + Serial.print ("Found "); + Serial.print (count); // 11 in this case + Serial.println (" matches."); + +} // end of setup + +void loop () {} diff --git a/lib/Regexp/keywords.txt b/lib/Regexp/keywords.txt new file mode 100644 index 000000000..27950a0f1 --- /dev/null +++ b/lib/Regexp/keywords.txt @@ -0,0 +1,9 @@ +MatchState KEYWORD1 +Match KEYWORD2 +Target KEYWORD2 +GetMatch KEYWORD2 +GetCapture KEYWORD2 +GetResult KEYWORD2 +MatchCount KEYWORD2 +GlobalMatch KEYWORD2 +GlobalReplace KEYWORD2 diff --git a/lib/Regexp/library.properties b/lib/Regexp/library.properties new file mode 100644 index 000000000..a4c7a89f1 --- /dev/null +++ b/lib/Regexp/library.properties @@ -0,0 +1,9 @@ +name=Regexp +version=0.1.0 +author=Nick Gammon +maintainer=Nick Gammon +sentence=Regular expression parser for microcontrollers +paragraph=Based upon Lua implementation +category=Uncategorized +url=https://github.com/nickgammon/Regexp +architectures=* diff --git a/lib/Regexp/src/Regexp.cpp b/lib/Regexp/src/Regexp.cpp new file mode 100644 index 000000000..472997050 --- /dev/null +++ b/lib/Regexp/src/Regexp.cpp @@ -0,0 +1,747 @@ +/* + +Regular-expression matching library for Arduino. + +Written by Nick Gammon. +Date: 30 April 2011 + +Heavily based on the Lua regular expression matching library written by Roberto Ierusalimschy. + +Adapted to run on the Arduino by Nick Gammon. + +VERSION + + Version 1.0 - 30th April 2011 : initial release. + Version 1.1 - 1st May 2011 : added some helper functions, made more modular. + Version 1.2 - 19th May 2011 : added more helper functions for replacing etc. + + +LICENSE + + +Copyright © 1994–2010 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE + OR OTHER DEALINGS IN THE SOFTWARE. + + +USAGE + + + Find the first match of the regular expression "pattern" in the supplied string, starting at position "index". + + If found, returns REGEXP_MATCHED (1). + + Also match_start and match_len in the MatchState structure are set to the start offset and length of the match. + + The capture in the MatchState structure has the locations and lengths of each capture. + + If not found, returns REGEXP_NOMATCH (0). + + On a parsing error (eg. trailing % symbol) returns a negative number. + + +EXAMPLE OF CALLING ON THE ARDUINO + +// ------------------------------------- // + +#include + +void setup () +{ + + Serial.begin (115200); + Serial.println (); + + MatchState ms; + char buf [100]; // large enough to hold expected string, or malloc it + + // string we are searching + ms.Target ("Testing: answer=42"); + + // search it + char result = ms.Match ("(%a+)=(%d+)", 0); + + // check results + + switch (result) + { + case REGEXP_MATCHED: + + Serial.println ("-----"); + Serial.print ("Matched on: "); + Serial.println (ms.GetMatch (buf)); + + // matching offsets in ms.capture + + Serial.print ("Captures: "); + Serial.println (ms.level); + + for (int j = 0; j < ms.level; j++) + { + Serial.print ("Capture number: "); + Serial.println (j + 1, DEC); + Serial.print ("Text: '"); + Serial.print (ms.GetCapture (buf, j)); + Serial.println ("'"); + + } + break; + + case REGEXP_NOMATCH: + Serial.println ("No match."); + break; + + default: + Serial.print ("Regexp error: "); + Serial.println (result, DEC); + break; + + } // end of switch + +} // end of setup + +void loop () {} // end of loop + +// ------------------------------------- // + + +PATTERNS + + Patterns + + The standard patterns (character classes) you can search for are: + + + . --- (a dot) represents all characters. + %a --- all letters. + %c --- all control characters. + %d --- all digits. + %l --- all lowercase letters. + %p --- all punctuation characters. + %s --- all space characters. + %u --- all uppercase letters. + %w --- all alphanumeric characters. + %x --- all hexadecimal digits. + %z --- the character with hex representation 0x00 (null). + %% --- a single '%' character. + + %1 --- captured pattern 1. + %2 --- captured pattern 2 (and so on). + %f[s] transition from not in set 's' to in set 's'. + %b() balanced pair ( ... ) + + + Important! - the uppercase versions of the above represent the complement of the class. + eg. %U represents everything except uppercase letters, %D represents everything except digits. + + There are some "magic characters" (such as %) that have special meanings. These are: + + + ^ $ ( ) % . [ ] * + - ? + + + If you want to use those in a pattern (as themselves) you must precede them by a % symbol. + + eg. %% would match a single % + + You can build your own pattern classes (sets) by using square brackets, eg. + + + [abc] ---> matches a, b or c + [a-z] ---> matches lowercase letters (same as %l) + [^abc] ---> matches anything except a, b or c + [%a%d] ---> matches all letters and digits + + [%a%d_] ---> matches all letters, digits and underscore + [%[%]] ---> matches square brackets (had to escape them with %) + + + You can use pattern classes in the form %x in the set. + If you use other characters (like periods and brackets, etc.) they are simply themselves. + + You can specify a range of character inside a set by using simple characters (not pattern classes like %a) separated by a hyphen. + For example, [A-Z] or [0-9]. These can be combined with other things. For example [A-Z0-9] or [A-Z,.]. + + A end-points of a range must be given in ascending order. That is, [A-Z] would match upper-case letters, but [Z-A] would not match anything. + + You can negate a set by starting it with a "^" symbol, thus [^0-9] is everything except the digits 0 to 9. + The negation applies to the whole set, so [^%a%d] would match anything except letters or digits. + In anywhere except the first position of a set, the "^" symbol is simply itself. + + Inside a set (that is a sequence delimited by square brackets) the only "magic" characters are: + + ] ---> to end the set, unless preceded by % + % ---> to introduce a character class (like %a), or magic character (like "]") + ^ ---> in the first position only, to negate the set (eg. [^A-Z) + - ---> between two characters, to specify a range (eg. [A-F]) + + + Thus, inside a set, characters like "." and "?" are just themselves. + + The repetition characters, which can follow a character, class or set, are: + + + + ---> 1 or more repetitions (greedy) + * ---> 0 or more repetitions (greedy) + + - ---> 0 or more repetitions (non greedy) + ? ---> 0 or 1 repetition only + + + A "greedy" match will match on as many characters as possible, a non-greedy one will match on as few as possible. + + The standard "anchor" characters apply: + + + ^ ---> anchor to start of subject string + $ ---> anchor to end of subject string + + + You can also use round brackets to specify "captures": + + + You see (.*) here + + + Here, whatever matches (.*) becomes the first pattern. + + You can also refer to matched substrings (captures) later on in an expression: + + eg. This would match: + + string = "You see dogs and dogs" + regexp = "You see (.*) and %1" + + + This example shows how you can look for a repetition of a word matched earlier, whatever that word was ("dogs" in this case). + + As a special case, an empty capture string returns as the captured pattern, the position of itself in the string. eg. + + string = "You see dogs and dogs" + regexp = "You .* ()dogs .*" + + This would return a capture with an offset of 8, and a length of CAP_POSITION (-2) + + Finally you can look for nested "balanced" things (such as parentheses) by using %b, like this: + + + string = "I see a (big fish (swimming) in the pond) here" + regexp = "%b()" + + + After %b you put 2 characters, which indicate the start and end of the balanced pair. + If it finds a nested version it keeps processing until we are back at the top level. + In this case the matching string was "(big fish (swimming) in the pond)". + + +*/ + + +#include +#include +#include +#include "Regexp.h" + +// for throwing errors +static jmp_buf regexp_error_return; +typedef unsigned char byte; + +// error codes raised during regexp processing +static byte error (const char err) +{ + // does not return + longjmp (regexp_error_return, err); + return 0; // keep compiler happy +} // end of error + +static int check_capture (MatchState *ms, int l) { + l -= '1'; + if (l < 0 || l >= ms->level || ms->capture[l].len == CAP_UNFINISHED) + return error(ERR_INVALID_CAPTURE_INDEX); + return l; +} // end of check_capture + +static int capture_to_close (MatchState *ms) { + int level = ms->level; + for (level--; level>=0; level--) + if (ms->capture[level].len == CAP_UNFINISHED) return level; + return error(ERR_INVALID_PATTERN_CAPTURE); +} // end of capture_to_close + +static const char *classend (MatchState *ms, const char *p) { + switch (*p++) { + case REGEXP_ESC: { + if (*p == '\0') + error(ERR_MALFORMED_PATTERN_ENDS_WITH_ESCAPE); + return p+1; + } + case '[': { + if (*p == '^') p++; + do { /* look for a `]' */ + if (*p == '\0') + error(ERR_MALFORMED_PATTERN_ENDS_WITH_RH_SQUARE_BRACKET); + if (*(p++) == REGEXP_ESC && *p != '\0') + p++; /* skip escapes (e.g. `%]') */ + } while (*p != ']'); + return p+1; + } + default: { + return p; + } + } +} // end of classend + + +static int match_class (int c, int cl) { + int res; + switch (tolower(cl)) { + case 'a' : res = isalpha(c); break; + case 'c' : res = iscntrl(c); break; + case 'd' : res = isdigit(c); break; + case 'l' : res = islower(c); break; + case 'p' : res = ispunct(c); break; + case 's' : res = isspace(c); break; + case 'u' : res = isupper(c); break; + case 'w' : res = isalnum(c); break; + case 'x' : res = isxdigit(c); break; + case 'z' : res = (c == 0); break; + default: return (cl == c); + } + return (islower(cl) ? res : !res); +} // end of match_class + + +static int matchbracketclass (int c, const char *p, const char *ec) { + int sig = 1; + if (*(p+1) == '^') { + sig = 0; + p++; /* skip the `^' */ + } + while (++p < ec) { + if (*p == REGEXP_ESC) { + p++; + if (match_class(c, uchar(*p))) + return sig; + } + else if ((*(p+1) == '-') && (p+2 < ec)) { + p+=2; + if (uchar(*(p-2)) <= c && c <= uchar(*p)) + return sig; + } + else if (uchar(*p) == c) return sig; + } + return !sig; +} // end of matchbracketclass + + +static int singlematch (int c, const char *p, const char *ep) { + switch (*p) { + case '.': return 1; /* matches any char */ + case REGEXP_ESC: return match_class(c, uchar(*(p+1))); + case '[': return matchbracketclass(c, p, ep-1); + default: return (uchar(*p) == c); + } +} // end of singlematch + + +static const char *match (MatchState *ms, const char *s, const char *p); + + +static const char *matchbalance (MatchState *ms, const char *s, + const char *p) { + if (*p == 0 || *(p+1) == 0) + error(ERR_UNBALANCED_PATTERN); + if (*s != *p) return NULL; + else { + int b = *p; + int e = *(p+1); + int cont = 1; + while (++s < ms->src_end) { + if (*s == e) { + if (--cont == 0) return s+1; + } + else if (*s == b) cont++; + } + } + return NULL; /* string ends out of balance */ +} // end of matchbalance + + +static const char *max_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + int i = 0; /* counts maximum expand for item */ + while ((s+i)src_end && singlematch(uchar(*(s+i)), p, ep)) + i++; + /* keeps trying to match with the maximum repetitions */ + while (i>=0) { + const char *res = match(ms, (s+i), ep+1); + if (res) return res; + i--; /* else didn't match; reduce 1 repetition to try again */ + } + return NULL; +} // end of max_expand + + +static const char *min_expand (MatchState *ms, const char *s, + const char *p, const char *ep) { + for (;;) { + const char *res = match(ms, s, ep+1); + if (res != NULL) + return res; + else if (ssrc_end && singlematch(uchar(*s), p, ep)) + s++; /* try with one more repetition */ + else return NULL; + } +} // end of min_expand + + +static const char *start_capture (MatchState *ms, const char *s, + const char *p, int what) { + const char *res; + int level = ms->level; + if (level >= MAXCAPTURES) error(ERR_TOO_MANY_CAPTURES); + ms->capture[level].init = s; + ms->capture[level].len = what; + ms->level = level+1; + if ((res=match(ms, s, p)) == NULL) /* match failed? */ + ms->level--; /* undo capture */ + return res; +} // end of start_capture + + +static const char *end_capture (MatchState *ms, const char *s, + const char *p) { + int l = capture_to_close(ms); + const char *res; + ms->capture[l].len = s - ms->capture[l].init; /* close capture */ + if ((res = match(ms, s, p)) == NULL) /* match failed? */ + ms->capture[l].len = CAP_UNFINISHED; /* undo capture */ + return res; +} // end of end_capture + + +static const char *match_capture (MatchState *ms, const char *s, int l) { + size_t len; + l = check_capture(ms, l); + len = ms->capture[l].len; + if ((size_t)(ms->src_end-s) >= len && + memcmp(ms->capture[l].init, s, len) == 0) + return s+len; + else return NULL; +} // end of match_capture + + +static const char *match (MatchState *ms, const char *s, const char *p) { +init: /* using goto's to optimize tail recursion */ + switch (*p) { + case '(': { /* start capture */ + if (*(p+1) == ')') /* position capture? */ + return start_capture(ms, s, p+2, CAP_POSITION); + else + return start_capture(ms, s, p+1, CAP_UNFINISHED); + } + case ')': { /* end capture */ + return end_capture(ms, s, p+1); + } + case REGEXP_ESC: { + switch (*(p+1)) { + case 'b': { /* balanced string? */ + s = matchbalance(ms, s, p+2); + if (s == NULL) return NULL; + p+=4; goto init; /* else return match(ms, s, p+4); */ + } + case 'f': { /* frontier? */ + const char *ep; char previous; + p += 2; + if (*p != '[') + error(ERR_MISSING_LH_SQUARE_BRACKET_AFTER_ESC_F); + ep = classend(ms, p); /* points to what is next */ + previous = (s == ms->src) ? '\0' : *(s-1); + if (matchbracketclass(uchar(previous), p, ep-1) || + !matchbracketclass(uchar(*s), p, ep-1)) return NULL; + p=ep; goto init; /* else return match(ms, s, ep); */ + } + default: { + if (isdigit(uchar(*(p+1)))) { /* capture results (%0-%9)? */ + s = match_capture(ms, s, uchar(*(p+1))); + if (s == NULL) return NULL; + p+=2; goto init; /* else return match(ms, s, p+2) */ + } + goto dflt; /* case default */ + } + } + } + case '\0': { /* end of pattern */ + return s; /* match succeeded */ + } + case '$': { + if (*(p+1) == '\0') /* is the `$' the last char in pattern? */ + return (s == ms->src_end) ? s : NULL; /* check end of string */ + else goto dflt; + } + default: dflt: { /* it is a pattern item */ + const char *ep = classend(ms, p); /* points to what is next */ + int m = ssrc_end && singlematch(uchar(*s), p, ep); + switch (*ep) { + case '?': { /* optional */ + const char *res; + if (m && ((res=match(ms, s+1, ep+1)) != NULL)) + return res; + p=ep+1; goto init; /* else return match(ms, s, ep+1); */ + } + case '*': { /* 0 or more repetitions */ + return max_expand(ms, s, p, ep); + } + case '+': { /* 1 or more repetitions */ + return (m ? max_expand(ms, s+1, p, ep) : NULL); + } + case '-': { /* 0 or more repetitions (minimum) */ + return min_expand(ms, s, p, ep); + } + default: { + if (!m) return NULL; + s++; p=ep; goto init; /* else return match(ms, s+1, ep); */ + } + } + } + } +} // end of match + + +// functions below written by Nick Gammon ... + +char MatchState::Match (const char * pattern, unsigned int index) +{ + // set up for throwing errors + char rtn = setjmp (regexp_error_return); + + // error return + if (rtn) + return ((result = rtn)); + + if (!src) + error (ERR_NO_TARGET_STRING); + + if (index > src_len) + index = src_len; + + int anchor = (*pattern == '^') ? (pattern++, 1) : 0; + const char *s1 =src + index; + src_end = src + src_len; + + // iterate through target string, character by character unless anchored + do { + const char *res; + level = 0; + if ((res=match(this, s1, pattern)) != NULL) + { + MatchStart = s1 - src; + MatchLength = res - s1; + return (result = REGEXP_MATCHED); + } // end of match at this position + } while (s1++ < src_end && !anchor); + + return (result = REGEXP_NOMATCH); // no match + +} // end of regexp + +// set up the target string +void MatchState::Target (char * s) + { + Target (s, strlen (s)); + } // end of MatchState::Target + +void MatchState::Target (char * s, const unsigned int len) + { + src = s; + src_len = len; + result = REGEXP_NOMATCH; + } // end of MatchState::Target + +// copy the match string to user-supplied buffer +// buffer must be large enough to hold it +char * MatchState::GetMatch (char * s) const +{ + if (result != REGEXP_MATCHED) + s [0] = 0; + else + { + memcpy (s, &src [MatchStart], MatchLength); + s [MatchLength] = 0; // null-terminated string + } + return s; +} // end of MatchState::GetMatch + +// get one of the capture strings (zero-relative level) +// buffer must be large enough to hold it +char * MatchState::GetCapture (char * s, const int n) const +{ + if (result != REGEXP_MATCHED || n >= level || capture [n].len <= 0) + s [0] = 0; + else + { + memcpy (s, capture [n].init, capture [n].len); + s [capture [n].len] = 0; // null-terminated string + } + return s; +} // end of MatchState::GetCapture + +String MatchState::GetCapture (const int n) const +{ + if (result != REGEXP_MATCHED || n >= level || capture [n].len <= 0) + { + return ""; + } + String s; + s.reserve(capture [n].len); + for (int i = 0; i < capture [n].len; ++i) + { + s += capture [n].init[i]; + } + return s; +} + +// match repeatedly on a string, return count of matches +unsigned int MatchState::MatchCount (const char * pattern) +{ + unsigned int count = 0; + + // keep matching until we run out of matches + for (unsigned int index = 0; + Match (pattern, index) > 0 && + index < src_len; // otherwise empty matches loop + count++) + // increment index ready for next time, go forwards at least one byte + index = MatchStart + (MatchLength == 0 ? 1 : MatchLength); + + return count; + +} // end of MatchState::MatchCount + +// match repeatedly on a string, call function f for each match +unsigned int MatchState::GlobalMatch (const char * pattern, GlobalMatchCallback f) +{ + unsigned int count = 0; + + // keep matching until we run out of matches + for (unsigned int index = 0; + Match (pattern, index) > 0; + count++) + { + f (& src [MatchStart], MatchLength, *this); + // increment index ready for next time, go forwards at least one byte + index = MatchStart + (MatchLength == 0 ? 1 : MatchLength); + } // end of for each match + return count; + +} // end of MatchState::GlobalMatch + +// match repeatedly on a string, call function f for each match +// f sets replacement string, incorporate replacement and continue +// maximum of max_count replacements if max_count > 0 +// replacement string in GlobalReplaceCallback must stay in scope (eg. static string or literal) +unsigned int MatchState::GlobalReplace (const char * pattern, GlobalReplaceCallback f, const unsigned int max_count) +{ + unsigned int count = 0; + + // keep matching until we run out of matches + for (unsigned int index = 0; + Match (pattern, index) > 0 && // stop when no match + index < src_len && // otherwise empty matches loop + (max_count == 0 || count < max_count); // stop when count reached + count++) + { + // default is to replace with self + const char * replacement = &src [MatchStart]; + unsigned int replacement_length = MatchLength; + + // increment index ready for next time, go forwards at least one byte + if (MatchLength == 0) + index = MatchStart + 1; // go forwards at least one byte or we will loop forever + else + { + // increment index ready for next time, + index = MatchStart + MatchLength; + + // call function to find replacement text + f (&src [MatchStart], MatchLength, replacement, replacement_length, *this); + + // see how much memory we need to move + int lengthDiff = MatchLength - replacement_length; + + // copy the rest of the buffer backwards/forwards to allow for the length difference + memmove (&src [index - lengthDiff], &src [index], src_len - index); + + // copy in the replacement + memmove (&src [MatchStart], replacement, replacement_length); + + // adjust the index for the next search + index -= lengthDiff; + // and the length of the source + src_len -= lengthDiff; + } // end if matching at least one byte + } // end of for each match + + // put a terminating null in + src [src_len] = 0; + return count; +} // end of MatchState::GlobalReplace + + +// match repeatedly on a string, replaces with replacement string for each match +// maximum of max_count replacements if max_count > 0 +// replacement string in GlobalReplaceCallback must stay in scope (eg. static string or literal) +unsigned int MatchState::GlobalReplace (const char * pattern, const char * replacement, const unsigned int max_count) +{ + unsigned int count = 0; + unsigned int replacement_length = strlen (replacement); + + // keep matching until we run out of matches + for (unsigned int index = 0; + Match (pattern, index) > 0 && // stop when no match + index < src_len && // otherwise empty matches loop + (max_count == 0 || count < max_count); // stop when count reached + count++) + { + if (MatchLength == 0) + index = MatchStart + 1; // go forwards at least one byte or we will loop forever + else + { + // increment index ready for next time, + index = MatchStart + MatchLength; + + // see how much memory we need to move + int lengthDiff = MatchLength - replacement_length; + + // copy the rest of the buffer backwards/forwards to allow for the length difference + memmove (&src [index - lengthDiff], &src [index], src_len - index); + + // copy in the replacement + memmove (&src [MatchStart], replacement, replacement_length); + + // adjust the index for the next search + index -= lengthDiff; + // and the length of the source + src_len -= lengthDiff; + } // end if matching at least one byte + + } // end of for each match + + // put a terminating null in + src [src_len] = 0; + return count; +} // end of MatchState::GlobalReplace + diff --git a/lib/Regexp/src/Regexp.h b/lib/Regexp/src/Regexp.h new file mode 100644 index 000000000..556872cd2 --- /dev/null +++ b/lib/Regexp/src/Regexp.h @@ -0,0 +1,134 @@ +/* + + Regular-expression matching library for Arduino. + + Written by Nick Gammon. + Date: 30 April 2011 + + Heavily based on the Lua regular expression matching library written by Roberto Ierusalimschy. + + Adapted to run on the Arduino by Nick Gammon. + + + VERSION + + Version 1.0 - 30th April 2011 : initial release. + Version 1.1 - 1st May 2011 : added some helper functions, made more modular. + Version 1.2 - 19th May 2011 : added more helper functions for replacing etc. + Version 1.2a - 12th March 2020 : TD-er: Added functions to get String captures + proper init of vars. + + + */ + +#pragma once + +#include + +// Maximum of captures we can return. +// Increase if you need more, decrease to save memory. +#define MAXCAPTURES 32 + +// the "magic escape" character +#define REGEXP_ESC '%' + +// special characters that have to be escaped +// (not used in the library, but you might need this) +#define REGEXP_SPECIALS "^$*+?.([%-" + +// Result codes from calling regexp: + +// we got a match +#define REGEXP_MATCHED 1 + +// no match, or not attempted to match yet +#define REGEXP_NOMATCH 0 + +// errors when matching +#define ERR_INVALID_CAPTURE_INDEX -1 +#define ERR_INVALID_PATTERN_CAPTURE -2 +#define ERR_MALFORMED_PATTERN_ENDS_WITH_ESCAPE -3 +#define ERR_MALFORMED_PATTERN_ENDS_WITH_RH_SQUARE_BRACKET -4 +#define ERR_UNBALANCED_PATTERN -5 +#define ERR_TOO_MANY_CAPTURES -6 +#define ERR_MISSING_LH_SQUARE_BRACKET_AFTER_ESC_F -7 +#define ERR_NO_TARGET_STRING -8 + + +/* macro to `unsign' a character */ +#define uchar(c) ((unsigned char)(c)) + +// special capture "lengths" +#define CAP_UNFINISHED (-1) +#define CAP_POSITION (-2) + +class MatchState; // forward definition for the callback routines + +typedef void (*GlobalMatchCallback) (const char * match, // matching string (not null-terminated) + const unsigned int length, // length of matching string + const MatchState & ms); // MatchState in use (to get captures) +typedef void (*GlobalReplaceCallback) (const char * match, // matching string (not null-terminated) + const unsigned int length, // length of matching string + const char * & replacement, + unsigned int & replacement_length, + const MatchState & ms); // MatchState in use (to get captures) +typedef class MatchState { +private: + + char result; // result of last Match call + +public: + + MatchState () : result (REGEXP_NOMATCH), src (0) {}; // constructor + MatchState (char * s) : result (REGEXP_NOMATCH) + { Target (s); }; // constructor from null-terminated string + MatchState (char * s, const unsigned int len) : result (REGEXP_NOMATCH) + { Target (s, len); }; // constructor from string and length + + // supply these two: + char *src = nullptr; /* source string */ + unsigned int src_len = 0; /* length of source string */ + + // used internally + char *src_end = nullptr; /* end of source string */ + + // returned fields: + + unsigned int MatchStart = 0; // zero-relative offset of start of match + unsigned int MatchLength = 0; // length of match + + int level; /* total number of captures in array below (finished or unfinished) */ + + // capture addresses and lengths + struct { + const char *init; + int len; // might be CAP_UNFINISHED or CAP_POSITION + } capture[MAXCAPTURES]; + + // add target string, null-terminated + void Target (char * s); + // add target string, with specified length + void Target (char * s, const unsigned int len); + // do a match on a supplied pattern and zero-relative starting point + char Match (const char * pattern, unsigned int index = 0); + // return the matching string + char * GetMatch (char * s) const; + // return capture string n + char * GetCapture (char * s, const int n) const; + String GetCapture (const int n) const; + // get result of previous match + char GetResult () const { return result; } + + // count number of matches on a supplied pattern + unsigned int MatchCount (const char * pattern); + // iterate with a supplied pattern, call function f for each match + // returns count of matches + unsigned int GlobalMatch (const char * pattern, GlobalMatchCallback f); + // iterate with a supplied pattern, call function f for each match, maximum of max_count matches if max_count > 0 + // returns count of replacements + unsigned int GlobalReplace (const char * pattern, GlobalReplaceCallback f, const unsigned int max_count = 0); + // iterate with a supplied pattern, replaces with replacement string, maximum of max_count matches if max_count > 0 + // returns count of replacements + unsigned int GlobalReplace (const char * pattern, const char * replacement, const unsigned int max_count = 0); + +} MatchState; + diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index b9df27f99..ade9c4545 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -125,24 +125,25 @@ build_flags = ${esp82xx_2_6_x.build_flags} -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 [core_2_6_3] -platform = espressif8266@2.3.2 +platform = espressif8266@2.4.0 platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.6.3 build_flags = ${esp82xx_2_6_x.build_flags} -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_191122 [core_2_6_3_sdk3] -platform = espressif8266@2.3.2 +platform = espressif8266@2.4.0 platform_packages = framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.6.3 build_flags = ${esp82xx_2_6_x.build_flags} -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 [core_stage] -platform = https://github.com/platformio/platform-espressif8266.git#feature/stage +platform = https://github.com/platformio/platform-espressif8266.git build_flags = ${esp82xx_2_6_x.build_flags} - -DARDUINO_ESP8266_RELEASE='"2.7.0-dev stage"' -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_191105 +platform_packages = + framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git [core_esp32_1_11_2] platform = espressif32@1.11.2 diff --git a/platformio_esp82xx_base.ini b/platformio_esp82xx_base.ini index 25762dee1..d585c96bf 100644 --- a/platformio_esp82xx_base.ini +++ b/platformio_esp82xx_base.ini @@ -21,6 +21,7 @@ platform_packages = ${core_2_6_3_sdk3.platform_packages} build_unflags = build_flags = ${core_stage.build_flags} platform = ${core_stage.platform} +platform_packages = ${core_stage.platform_packages} @@ -47,6 +48,7 @@ build_flags = ${regular_platform.build_flags} [normal_beta] platform = ${beta_platform.platform} +platform_packages = ${beta_platform.platform_packages} build_flags = ${beta_platform.build_flags} @@ -62,6 +64,7 @@ build_flags = ${regular_platform.build_flags} -DPLUGIN_BUILD_TESTI [testing_beta] platform = ${beta_platform.platform} +platform_packages = ${beta_platform.platform_packages} build_flags = ${beta_platform.build_flags} -DPLUGIN_BUILD_TESTING diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index b09749353..239c35eb5 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -27,6 +27,7 @@ extra_scripts = pre:pre_custom_esp82xx.py [env:custom_sdk3_ESP8266_4M1M] extends = esp8266_4M1M platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8266_4M1M.build_flags} -DPLUGIN_BUILD_CUSTOM @@ -37,6 +38,7 @@ extra_scripts = pre:pre_custom_esp82xx.py [env:custom_beta_ESP8266_4M1M] extends = esp8266_4M1M platform = ${beta_platform.platform} +platform_packages = ${beta_platform.platform_packages} build_flags = ${beta_platform.build_flags} ${esp8266_4M1M.build_flags} -DPLUGIN_BUILD_CUSTOM @@ -84,6 +86,7 @@ build_flags = ${regular_platform.build_flags} [env:normal_sdk3_ESP8266_1M] extends = esp8266_1M platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8266_1M.build_flags} @@ -184,6 +187,7 @@ build_flags = ${regular_platform.build_flags} [env:minimal_core_263_sdk3_ESP8266_1M_OTA_Domoticz] extends = esp8266_1M_OTA platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8266_1M_OTA.build_flags} ${minimal_OTA_domoticz.build_flags} @@ -191,6 +195,7 @@ build_flags = ${core_2_6_3_sdk3.build_flags} [env:minimal_core_263_sdk3_ESP8285_1M_OTA_Domoticz] extends = esp8285_1M_OTA platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8285_1M_OTA.build_flags} ${minimal_OTA_domoticz.build_flags} @@ -215,6 +220,7 @@ build_flags = ${regular_platform.build_flags} [env:minimal_core_263_sdk3_ESP8266_1M_OTA_FHEM_HA] extends = esp8266_1M_OTA platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8266_1M_OTA.build_flags} ${minimal_OTA_FHEM_HA.build_flags} @@ -222,6 +228,7 @@ build_flags = ${core_2_6_3_sdk3.build_flags} [env:minimal_core_263_sdk3_ESP8285_1M_OTA_FHEM_HA] extends = esp8285_1M_OTA platform = ${core_2_6_3_sdk3.platform} +platform_packages = ${core_2_6_3_sdk3.platform_packages} build_flags = ${core_2_6_3_sdk3.build_flags} ${esp8285_1M_OTA.build_flags} ${minimal_OTA_FHEM_HA.build_flags} @@ -239,6 +246,7 @@ build_flags = ${core_2_6_3_sdk3.build_flags} [env:minimal_IRext_ESP8266_1M] extends = esp8266_1M platform = ${minimal_ir_extended.platform} +platform_packages = ${minimal_ir_extended.platform_packages} lib_ignore = ${minimal_ir_extended.lib_ignore} build_flags = ${minimal_ir_extended.build_flags} ${esp8266_1M.build_flags} @@ -252,6 +260,7 @@ build_unflags = ${esp8266_1M_OTA.build_unflags} -DPLUGIN_BUILD_NORMA [env:minimal_IRext_ESP8266_4M1M] extends = esp8266_4M1M platform = ${minimal_ir_extended.platform} +platform_packages = ${minimal_ir_extended.platform_packages} lib_ignore = ${minimal_ir_extended.lib_ignore} build_flags = ${minimal_ir_extended.build_flags} ${esp8266_4M1M.build_flags} @@ -260,6 +269,7 @@ build_flags = ${minimal_ir_extended.build_flags} [env:minimal_IRext_ESP8266_4M2M] extends = esp8266_4M2M platform = ${minimal_ir_extended.platform} +platform_packages = ${minimal_ir_extended.platform_packages} lib_ignore = ${minimal_ir_extended.lib_ignore} build_flags = ${minimal_ir_extended.build_flags} ${esp8266_4M2M.build_flags} @@ -272,6 +282,7 @@ build_flags = ${minimal_ir_extended.build_flags} [env:normal_IRext_no_rx_ESP8266_4M2M] extends = esp8266_4M2M platform = ${normal_ir_extended_no_rx.platform} +platform_packages = ${normal_ir_extended_no_rx.platform_packages} lib_ignore = ${normal_ir_extended_no_rx.lib_ignore} build_flags = ${normal_ir_extended_no_rx.build_flags} ${esp8266_4M2M.build_flags} @@ -288,6 +299,7 @@ build_flags = ${normal_ir_extended_no_rx.build_flags} [env:test_ESP8266_4M1M_VCC] extends = esp8266_4M1M platform = ${testing.platform} +platform_packages = ${testing.platform_packages} build_flags = ${testing.build_flags} ${esp8266_4M1M.build_flags} -DFEATURE_ADC_VCC=true @@ -296,6 +308,7 @@ build_flags = ${testing.build_flags} [env:test_ESP8266_4M1M_VCC_MDNS_SD] extends = esp8266_4M1M platform = ${testing.platform} +platform_packages = ${testing.platform_packages} build_flags = ${testing.build_flags} ${esp8266_4M1M.build_flags} -DFEATURE_ADC_VCC=true @@ -307,6 +320,7 @@ lib_ignore = ESP32_ping, ESP32WebServer, IRremoteESP8266, Heatpum [env:test_beta_ESP8266_4M1M] extends = esp8266_4M1M platform = ${testing_beta.platform} +platform_packages = ${testing_beta.platform_packages} build_flags = ${testing_beta.build_flags} ${esp8266_4M1M.build_flags} @@ -314,6 +328,7 @@ build_flags = ${testing_beta.build_flags} [env:test_beta_ESP8266_16M] extends = esp8266_16M platform = ${testing_beta.platform} +platform_packages = ${testing_beta.platform_packages} build_flags = ${testing_beta.build_flags} ${esp8266_16M.build_flags} -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22y @@ -333,6 +348,7 @@ build_flags = ${testing_beta.build_flags} [env:dev_ESP8266_4M1M] extends = esp8266_4M1M platform = ${dev.platform} +platform_packages = ${dev.platform_packages} build_flags = ${dev.build_flags} ${esp8266_4M1M.build_flags} diff --git a/platformio_special_envs.ini b/platformio_special_envs.ini index a04259086..00b417f71 100644 --- a/platformio_special_envs.ini +++ b/platformio_special_envs.ini @@ -27,6 +27,7 @@ extra_scripts = pre:pre_custom_esp82xx.py [env:spec_debug_beta_custom_ESP8266_4M1M] extends = esp8266_4M1M, debug_pio platform = ${beta_platform.platform} +platform_packages = ${beta_platform.platform_packages} build_flags = ${beta_platform.build_flags} ${debug_pio.build_flags} ${esp8266_4M1M.build_flags} diff --git a/src/Controller.ino b/src/Controller.ino index 98a7df4cf..478311725 100644 --- a/src/Controller.ino +++ b/src/Controller.ino @@ -488,21 +488,23 @@ void SensorSendTask(taskIndex_t TaskIndex) if (success) { - START_TIMER; - for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) - { - if (ExtraTaskSettings.TaskDeviceFormula[varNr][0] != 0) + if (Device[DeviceIndex].FormulaOption) { + START_TIMER; + for (byte varNr = 0; varNr < VARS_PER_TASK; varNr++) { - String formula = ExtraTaskSettings.TaskDeviceFormula[varNr]; - formula.replace(F("%pvalue%"), String(preValue[varNr])); - formula.replace(F("%value%"), String(UserVar[varIndex + varNr])); - float result = 0; - byte error = Calculate(formula.c_str(), &result); - if (error == 0) - UserVar[varIndex + varNr] = result; + if (ExtraTaskSettings.TaskDeviceFormula[varNr][0] != 0) + { + String formula = ExtraTaskSettings.TaskDeviceFormula[varNr]; + formula.replace(F("%pvalue%"), String(preValue[varNr])); + formula.replace(F("%value%"), String(UserVar[varIndex + varNr])); + float result = 0; + byte error = Calculate(formula.c_str(), &result); + if (error == 0) + UserVar[varIndex + varNr] = result; + } } + STOP_TIMER(COMPUTE_FORMULA_STATS); } - STOP_TIMER(COMPUTE_FORMULA_STATS); sendData(&TempEvent); } } diff --git a/src/Custom-sample.h b/src/Custom-sample.h index 573ed98bd..2319d686f 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -96,6 +96,7 @@ #define DEFAULT_PIN_I2C_SDA 4 #define DEFAULT_PIN_I2C_SCL 5 +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips #define DEFAULT_PIN_STATUS_LED (-1) #define DEFAULT_PIN_STATUS_LED_INVERSED true diff --git a/src/ESPEasy.ino b/src/ESPEasy.ino index 8979dc359..d5df04936 100644 --- a/src/ESPEasy.ino +++ b/src/ESPEasy.ino @@ -371,6 +371,8 @@ void setup() WiFiConnectRelaxed(); + setWebserverRunning(true); + #ifdef FEATURE_REPORTING ReportStatus(); #endif diff --git a/src/ESPEasyStorage.ino b/src/ESPEasyStorage.ino index f43354ba2..55a66ba84 100644 --- a/src/ESPEasyStorage.ino +++ b/src/ESPEasyStorage.ino @@ -46,7 +46,7 @@ String flashGuard() // use this in function that can return an error string. it automaticly returns with an error string if there where too many flash writes. #define FLASH_GUARD() { String flashErr = flashGuard(); \ - if (flashErr.length()) return (flashErr); } + if (flashErr.length()) return flashErr; } String appendLineToFile(const String& fname, const String& line) { @@ -149,6 +149,9 @@ String BuildFixes() Settings.ResetFactoryDefaultPreference = 0; Settings.OldRulesEngine(DEFAULT_RULES_OLDENGINE); } + if (Settings.Build < 20105) { + Settings.I2C_clockSpeed = 400000; + } Settings.Build = BUILD; return SaveSettings(); @@ -296,6 +299,7 @@ void afterloadSettings() { ResetFactoryDefaultPreference = Settings.ResetFactoryDefaultPreference; } msecTimerHandler.setEcoMode(Settings.EcoPowerMode()); + if (!Settings.UseRules) { eventQueue.clear(); } @@ -439,8 +443,8 @@ bool getSettingsParameters(SettingsType settingsType, int index, int& max_index, case CustomTaskSettings_Type: { getSettingsParameters(TaskSettings_Type, index, max_index, offset, max_size, struct_size); - offset += (DAT_TASKS_CUSTOM_OFFSET); - max_size = DAT_TASKS_CUSTOM_SIZE; + offset += (DAT_TASKS_CUSTOM_OFFSET); + max_size = DAT_TASKS_CUSTOM_SIZE; break; // struct_size may differ. @@ -483,7 +487,7 @@ int getMaxFilePos(SettingsType settingsType) { int max_index, offset, max_size; int struct_size = 0; - getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size); + getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size); getSettingsParameters(settingsType, max_index - 1, offset, max_size); return offset + max_size - 1; } @@ -563,6 +567,7 @@ String LoadTaskSettings(taskIndex_t TaskIndex) if (ExtraTaskSettings.TaskIndex == TaskIndex) { return String(); // already loaded } + if (!validTaskIndex(TaskIndex)) { return String(); // Un-initialized task index. } @@ -601,6 +606,84 @@ String SaveCustomTaskSettings(taskIndex_t TaskIndex, byte *memAddress, int datas return SaveToFile(CustomTaskSettings_Type, TaskIndex, (char *)FILE_CONFIG, memAddress, datasize); } +/********************************************************************************************\ + Save array of Strings to Custom Task settings + Use maxStringLength = 0 to optimize for size (strings will be concatenated) + \*********************************************************************************************/ +String SaveCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t nrStrings, uint16_t maxStringLength) +{ + checkRAM(F("SaveCustomTaskSettings")); + const uint16_t bufferSize = 128; + + // FIXME TD-er: For now stack allocated, may need to be heap allocated? + byte buffer[bufferSize]; + + String result; + int writePos = 0; + uint16_t stringCount = 0; + uint16_t stringReadPos = 0; + uint16_t nextStringPos = 0; + uint16_t curStringLength = 0; + + if (maxStringLength != 0) { + // Specified string length, check given strings + for (int i = 0; i < nrStrings; ++i) { + if (strings[i].length() >= maxStringLength) { + result += getCustomTaskSettingsError(i); + } + } + } + + while (stringCount < nrStrings && writePos < DAT_TASKS_CUSTOM_SIZE) { + ZERO_FILL(buffer); + + for (int i = 0; i < bufferSize && stringCount < nrStrings; ++i) { + if (stringReadPos == 0) { + // We're at the start of a string + curStringLength = strings[stringCount].length(); + + if (maxStringLength != 0) { + if (curStringLength >= maxStringLength) { + curStringLength = maxStringLength - 1; + } + } + } + + uint16_t curPos = writePos + i; + + if (curPos >= nextStringPos) { + if (stringReadPos < curStringLength) { + buffer[i] = strings[stringCount][stringReadPos]; + ++stringReadPos; + } else { + buffer[i] = 0; + stringReadPos = 0; + ++stringCount; + + if (maxStringLength == 0) { + nextStringPos += curStringLength + 1; + } else { + nextStringPos += maxStringLength; + } + } + } + } + + // Buffer is filled, now write to flash + // As we write in parts, only count as single write. + if (RTC.flashDayCounter > 0) { + RTC.flashDayCounter--; + } + result += SaveToFile(CustomTaskSettings_Type, TaskIndex, (char *)FILE_CONFIG, &(buffer[0]), bufferSize, writePos); + writePos += bufferSize; + } + + if ((writePos >= DAT_TASKS_CUSTOM_SIZE) && (stringCount < nrStrings)) { + result += F("Error: Not all strings fit in custom task settings."); + } + return result; +} + String getCustomTaskSettingsError(byte varNr) { String error = F("Error: Text too long for line "); @@ -632,26 +715,59 @@ String LoadCustomTaskSettings(taskIndex_t TaskIndex, byte *memAddress, int datas /********************************************************************************************\ Load array of Strings from Custom Task settings + Use maxStringLength = 0 to optimize for size (strings will be concatenated) \*********************************************************************************************/ -String LoadCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t nrStrings, uint16_t maxStringLenght) +String LoadCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t nrStrings, uint16_t maxStringLength) { START_TIMER; checkRAM(F("LoadCustomTaskSettings")); - // FIXME TD-er: For now stack allocated, may need to be heap allocated? - if (maxStringLenght >= 128) { return F("Max 128 chars allowed"); } - char tmpStr[128]; - String result; + const uint16_t bufferSize = 128; - for (int i = 0; i < nrStrings; ++i) { + // FIXME TD-er: For now stack allocated, may need to be heap allocated? + if (maxStringLength >= bufferSize) { return F("Max 128 chars allowed"); } + char buffer[bufferSize]; + + String result; + uint16_t readPos = 0; + uint16_t nextStringPos = 0; + uint16_t stringCount = 0; + String tmpString; + tmpString.reserve(bufferSize); + + while (stringCount < nrStrings && readPos < DAT_TASKS_CUSTOM_SIZE) { result += LoadFromFile(CustomTaskSettings_Type, TaskIndex, (char *)FILE_CONFIG, - (byte *)&tmpStr, - maxStringLenght, - maxStringLenght * i); - tmpStr[maxStringLenght] = 0; // Terminate in case of uninitalized data - strings[i] = String(tmpStr); + (byte *)&buffer, + bufferSize, + readPos); + + for (int i = 0; i < bufferSize && stringCount < nrStrings; ++i) { + uint16_t curPos = readPos + i; + + if (curPos >= nextStringPos) { + if (buffer[i] == 0) { + if (maxStringLength != 0) { + // Specific string length, so we have to set the next string position. + nextStringPos += maxStringLength; + } + strings[stringCount] = tmpString; + tmpString = ""; + tmpString.reserve(bufferSize); + ++stringCount; + } else { + tmpString += buffer[i]; + } + } + } + readPos += bufferSize; + } + + if ((tmpString.length() != 0) && (stringCount < nrStrings)) { + result += F("Incomplete custom settings for task "); + result += (TaskIndex + 1); + strings[stringCount] = tmpString; } STOP_TIMER(LOAD_CUSTOM_TASK_STATS); return result; @@ -661,7 +777,7 @@ String LoadCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t Save Controller settings to SPIFFS \*********************************************************************************************/ String SaveControllerSettings(controllerIndex_t ControllerIndex, ControllerSettingsStruct& controller_settings) -{ +{ checkRAM(F("SaveControllerSettings")); controller_settings.validate(); // Make sure the saved controller settings have proper values. return SaveToFile(ControllerSettings_Type, ControllerIndex, @@ -758,11 +874,11 @@ String InitFile(const char *fname, int datasize) \*********************************************************************************************/ String SaveToFile(const char *fname, int index, const byte *memAddress, int datasize) { - return SaveToFile(fname, index, memAddress, datasize, "r+"); + return doSaveToFile(fname, index, memAddress, datasize, "r+"); } // See for mode description: https://github.com/esp8266/Arduino/blob/master/doc/filesystem.rst -String SaveToFile(const char *fname, int index, const byte *memAddress, int datasize, const char* mode) +String doSaveToFile(const char *fname, int index, const byte *memAddress, int datasize, const char *mode) { #ifndef ESP32 @@ -786,6 +902,7 @@ String SaveToFile(const char *fname, int index, const byte *memAddress, int data START_TIMER; checkRAM(F("SaveToFile")); FLASH_GUARD(); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("SaveToFile: free stack: "); log += getCurrentFreeStack(); @@ -820,6 +937,7 @@ String SaveToFile(const char *fname, int index, const byte *memAddress, int data } } f.close(); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("FILE : Saved "); log = log + fname; @@ -833,6 +951,7 @@ String SaveToFile(const char *fname, int index, const byte *memAddress, int data return log; } STOP_TIMER(SAVEFILE_STATS); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("SaveToFile: free stack after: "); log += getCurrentFreeStack(); @@ -962,6 +1081,10 @@ String LoadFromFile(SettingsType settingsType, int index, char *fname, byte *mem } String SaveToFile(SettingsType settingsType, int index, char *fname, byte *memAddress, int datasize) { + return SaveToFile(settingsType, index, fname, memAddress, datasize, 0); +} + +String SaveToFile(SettingsType settingsType, int index, char *fname, byte *memAddress, int datasize, int posInBlock) { bool read = false; int offset, max_size; @@ -969,10 +1092,10 @@ String SaveToFile(SettingsType settingsType, int index, char *fname, byte *memAd return getSettingsFileIndexRangeError(read, settingsType, index); } - if (datasize > max_size) { + if ((datasize > max_size) || ((posInBlock + datasize) > max_size)) { return getSettingsFileDatasizeError(read, settingsType, index, datasize, max_size); } - return SaveToFile(fname, offset, memAddress, datasize); + return SaveToFile(fname, offset + posInBlock, memAddress, datasize); } String ClearInFile(SettingsType settingsType, int index, char *fname) { diff --git a/src/ESPEasyWifi.ino b/src/ESPEasyWifi.ino index 1e50904df..9eec41d8d 100644 --- a/src/ESPEasyWifi.ino +++ b/src/ESPEasyWifi.ino @@ -85,14 +85,15 @@ bool WiFiConnected() { // For ESP82xx, do not rely on WiFi.status() with event based wifi. const bool validWiFi = (WiFi.RSSI() < 0) && WiFi.isConnected() && hasIPaddr(); - + // FIXME TD-er: Not sure if this is needed as we also set it when processing WiFi events. +/* if (wifiStatus != ESPEASY_WIFI_SERVICES_INITIALIZED) { if (validWiFi) { // Set internal wifiStatus and reset timer to disable AP mode markWiFi_services_initialized(); } } - +*/ if (wifiStatus == ESPEASY_WIFI_SERVICES_INITIALIZED) { if (validWiFi) { // Connected, thus disable any timer to start AP mode. (except when in WiFi setup mode) @@ -241,7 +242,6 @@ void resetWiFi() { processedScanDone = true; wifiConnectAttemptNeeded = true; WifiDisconnect(); -// setWebserverRunning(false); // setWifiMode(WIFI_OFF); diff --git a/src/ESPEasyWifi_ProcessEvent.ino b/src/ESPEasyWifi_ProcessEvent.ino index 1c5573c7f..120669308 100644 --- a/src/ESPEasyWifi_ProcessEvent.ino +++ b/src/ESPEasyWifi_ProcessEvent.ino @@ -128,7 +128,6 @@ void processDisconnect() { if (processedDisconnect) { return; } processedDisconnect = true; wifiStatus = ESPEASY_WIFI_DISCONNECTED; -// setWebserverRunning(false); delay(100); // FIXME TD-er: See https://github.com/letscontrolit/ESPEasy/issues/1987#issuecomment-451644424 if (Settings.UseRules) { @@ -312,7 +311,6 @@ void processConnectAPmode() { log += WiFi.softAPgetStationNum(); addLog(LOG_LEVEL_INFO, log); } - setWebserverRunning(true); // Start DNS, only used if the ESP has no valid WiFi config // It will reply with it's own address on all DNS requests @@ -420,5 +418,4 @@ void processScanDone() { void markWiFi_services_initialized() { wifiStatus = ESPEASY_WIFI_SERVICES_INITIALIZED; wifiConnectInProgress = false; - setWebserverRunning(true); } \ No newline at end of file diff --git a/src/ESPEasy_buildinfo.h b/src/ESPEasy_buildinfo.h index f8b2e1fb6..baaeaa71f 100644 --- a/src/ESPEasy_buildinfo.h +++ b/src/ESPEasy_buildinfo.h @@ -20,7 +20,7 @@ #endif // if defined(ESP32) -#define BUILD 20104 // git version e.g. "20103" can be read as "2.1.03" (stored in int16_t) +#define BUILD 20105 // git version e.g. "20103" can be read as "2.1.03" (stored in int16_t) #if defined(ESP8266) # define BUILD_NOTES " - Mega" #endif // if defined(ESP8266) diff --git a/src/ESPEasy_checks.ino b/src/ESPEasy_checks.ino index 700a7b1dc..ea444e1b8 100644 --- a/src/ESPEasy_checks.ino +++ b/src/ESPEasy_checks.ino @@ -34,7 +34,7 @@ template constexpr size_t offsetOf(U T::*member) void run_compiletime_checks() { check_size(); check_size(); - const unsigned int SettingsStructSize = (244 + 82 * TASKS_MAX); + const unsigned int SettingsStructSize = (248 + 82 * TASKS_MAX); check_size(); check_size(); check_size(); diff --git a/src/ESPEasy_fdwdecl.h b/src/ESPEasy_fdwdecl.h index ae75dce85..3774846f5 100644 --- a/src/ESPEasy_fdwdecl.h +++ b/src/ESPEasy_fdwdecl.h @@ -134,8 +134,12 @@ String formatIP(const IPAddress& ip); String toString(float value, byte decimals); String boolToString(bool value); bool isInt(const String& tBuf); +unsigned long hexToUL(const String& input_c); +unsigned long hexToUL(const String& input_c, size_t nrHexDecimals); +unsigned long hexToUL(const String& input_c, size_t startpos, size_t nrHexDecimals); String formatToHex(unsigned long value, const String& prefix); String formatToHex(unsigned long value); +String formatToHex_decimal(unsigned long value); String getNumerical(const String& tBuf, bool mustBeInteger); float getCPUload(); diff --git a/src/Hardware.ino b/src/Hardware.ino index 61c52e89e..f6e53bc73 100644 --- a/src/Hardware.ino +++ b/src/Hardware.ino @@ -49,11 +49,47 @@ void hardwareInit() pinMode(Settings.Pin_Reset, INPUT_PULLUP); } + initI2C(); + + // SPI Init + if (Settings.InitSPI) + { + SPI.setHwCs(false); + SPI.begin(); + String log = F("INIT : SPI Init (without CS)"); + addLog(LOG_LEVEL_INFO, log); + } + else + { + String log = F("INIT : SPI not enabled"); + addLog(LOG_LEVEL_INFO, log); + } + +#ifdef FEATURE_SD + + if (Settings.Pin_sd_cs >= 0) + { + if (SD.begin(Settings.Pin_sd_cs)) + { + String log = F("SD : Init OK"); + addLog(LOG_LEVEL_INFO, log); + } + else + { + String log = F("SD : Init failed"); + addLog(LOG_LEVEL_ERROR, log); + } + } +#endif // ifdef FEATURE_SD +} + +void initI2C() { // configure hardware pins according to eeprom settings. if (Settings.Pin_i2c_sda != -1) { String log = F("INIT : I2C"); addLog(LOG_LEVEL_INFO, log); + Wire.setClock(Settings.I2C_clockSpeed); Wire.begin(Settings.Pin_i2c_sda, Settings.Pin_i2c_scl); if (Settings.WireClockStretchLimit) @@ -90,37 +126,6 @@ void hardwareInit() } } } - - // SPI Init - if (Settings.InitSPI) - { - SPI.setHwCs(false); - SPI.begin(); - String log = F("INIT : SPI Init (without CS)"); - addLog(LOG_LEVEL_INFO, log); - } - else - { - String log = F("INIT : SPI not enabled"); - addLog(LOG_LEVEL_INFO, log); - } - -#ifdef FEATURE_SD - - if (Settings.Pin_sd_cs >= 0) - { - if (SD.begin(Settings.Pin_sd_cs)) - { - String log = F("SD : Init OK"); - addLog(LOG_LEVEL_INFO, log); - } - else - { - String log = F("SD : Init failed"); - addLog(LOG_LEVEL_ERROR, log); - } - } -#endif // ifdef FEATURE_SD } void checkResetFactoryPin() { diff --git a/src/Misc.ino b/src/Misc.ino index 5eac42305..d5ba02ab6 100644 --- a/src/Misc.ino +++ b/src/Misc.ino @@ -756,7 +756,10 @@ void statusLED(bool traffic) #if defined(ESP8266) analogWrite(Settings.Pin_status_led, pwm); - #endif + #endif // if defined(ESP8266) + #if defined(ESP32) + analogWriteESP32(Settings.Pin_status_led, pwm); + #endif // if defined(ESP32) } } @@ -1227,6 +1230,7 @@ void ResetFactory() Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; */ + Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; #ifdef PLUGIN_DESCR strcpy_P(Settings.Name, PSTR(PLUGIN_DESCR)); diff --git a/src/Serial.ino b/src/Serial.ino index 0087eca8b..5d3aafc2a 100644 --- a/src/Serial.ino +++ b/src/Serial.ino @@ -47,12 +47,12 @@ void addToSerialBuffer(const char *line) { const size_t line_length = strlen(line); int roomLeft = getMaxFreeBlock(); - if (roomLeft < 500) { + if (roomLeft < 1000) { roomLeft = 0; // Do not append to buffer. - } else if (roomLeft < 3000) { + } else if (roomLeft < 4000) { roomLeft = 128 - serialWriteBuffer.size(); // 1 buffer. } else { - roomLeft -= 3000; // leave some free for normal use. + roomLeft -= 4000; // leave some free for normal use. } if (roomLeft > 0) { diff --git a/src/StringConverter.ino b/src/StringConverter.ino index 05f4a6ac3..34ea53e0c 100644 --- a/src/StringConverter.ino +++ b/src/StringConverter.ino @@ -69,6 +69,34 @@ String formatMAC(const uint8_t *mac) { return String(str); } + +/********************************************************************************************\ + Handling HEX strings + \*********************************************************************************************/ + +// Convert max. 8 hex decimals to unsigned long +unsigned long hexToUL(const String& input_c, size_t nrHexDecimals) { + size_t nr_decimals = nrHexDecimals; + if (nr_decimals > 8) { + nr_decimals = 8; + } + size_t inputLength = input_c.length(); + if (nr_decimals > inputLength) { + nr_decimals = inputLength; + } + String tmp = input_c.substring(0, nr_decimals); + return strtoul(tmp.c_str(), 0, 16); +} + +unsigned long hexToUL(const String& input_c) { + return hexToUL(input_c, input_c.length()); +} + +unsigned long hexToUL(const String& input_c, size_t startpos, size_t nrHexDecimals) { + return hexToUL(input_c.substring(startpos, startpos + nrHexDecimals), nrHexDecimals); +} + + String formatToHex(unsigned long value, const String& prefix) { String result = prefix; String hex(value, HEX); diff --git a/src/WebServer_HardwarePage.ino b/src/WebServer_HardwarePage.ino index e0cac1262..14ba03f6f 100644 --- a/src/WebServer_HardwarePage.ino +++ b/src/WebServer_HardwarePage.ino @@ -18,6 +18,7 @@ void handle_hardware() { Settings.Pin_Reset = getFormItemInt(F("pres")); Settings.Pin_i2c_sda = getFormItemInt(F("psda")); Settings.Pin_i2c_scl = getFormItemInt(F("pscl")); + Settings.I2C_clockSpeed = getFormItemInt(F("pi2csp"), DEFAULT_I2C_CLOCK_SPEED); Settings.InitSPI = isFormItemChecked(F("initspi")); // SPI Init Settings.Pin_sd_cs = getFormItemInt(F("sd")); int gpio = 0; @@ -38,7 +39,12 @@ void handle_hardware() { } ++gpio; } - addHtmlError(SaveSettings()); + String error = SaveSettings(); + addHtmlError(error); + if (error.length() == 0) { + // Apply I2C settings. + initI2C(); + } } addHtml(F("
")); @@ -57,6 +63,9 @@ void handle_hardware() { addFormSubHeader(F("I2C Interface")); addFormPinSelectI2C(formatGpioName_bidirectional("SDA"), F("psda"), Settings.Pin_i2c_sda); addFormPinSelectI2C(formatGpioName_output("SCL"), F("pscl"), Settings.Pin_i2c_scl); + addFormNumericBox(F("Clock Speed"), F("pi2csp"), Settings.I2C_clockSpeed, 100, 3400000); + addUnit(F("Hz")); + addFormNote(F("Use 100 kHz for old I2C devices, 400 kHz is max for most.")); // SPI Init addFormSubHeader(F("SPI Interface")); diff --git a/src/WebServer_JSON.ino b/src/WebServer_JSON.ino index aff0d68c4..5625d65dd 100644 --- a/src/WebServer_JSON.ino +++ b/src/WebServer_JSON.ino @@ -241,8 +241,8 @@ void handle_json() if (!showSpecificTask) { addHtml(F("],\n")); + stream_last_json_object_value(F("TTL"), String(lowest_ttl_json * 1000)); } - stream_last_json_object_value(F("TTL"), String(lowest_ttl_json * 1000)); TXBuffer.endStream(); } diff --git a/src/WebServer_Markup.ino b/src/WebServer_Markup.ino index 9c1efc929..6fadffbf2 100644 --- a/src/WebServer_Markup.ino +++ b/src/WebServer_Markup.ino @@ -10,9 +10,22 @@ void addSelector(const String& id, int selectedIndex, boolean reloadonchange, bool enabled) +{ + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, enabled, F("wide")); +} + +void addSelector(const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + boolean reloadonchange, + bool enabled, + const String& classname) { // FIXME TD-er Change boolean to disabled - addSelector_Head(id, reloadonchange, !enabled); + addSelector_Head(id, classname, reloadonchange, !enabled); addSelector_options(optionCount, options, indices, attr, selectedIndex); addSelector_Foot(); } @@ -51,24 +64,30 @@ void addSelector_options(int optionCount, const String options[], const int indi } void addSelector_Head(const String& id, boolean reloadonchange) { - addSelector_Head(id, reloadonchange, false); + addSelector_Head(id, F("wide"), reloadonchange); } -void addSelector_Head(const String& id, boolean reloadonchange, bool disabled) +void addSelector_Head(const String& id, const String& classname, boolean reloadonchange) { + addSelector_Head(id, classname, reloadonchange, false); +} + +void addSelector_Head(const String& id, const String& classname, boolean reloadonchange, bool disabled) { if (reloadonchange) { - addSelector_Head(id, (const String)F("return dept_onchange(frmselect)"), disabled); + addSelector_Head(id, classname, (const String)F("return dept_onchange(frmselect)"), disabled); } else { - addSelector_Head(id, (const String)"", disabled); + addSelector_Head(id, classname, (const String)"", disabled); } } -void addSelector_Head(const String& id, const String& onChangeCall, bool disabled) +void addSelector_Head(const String& id, const String& onChangeCall, const String& classname, bool disabled) { { String html; html.reserve(32 + id.length()); - html += F("")); - addHtml(F("")); html_TR_TD(); { String html; html.reserve(64); - html += F("Current size: "); + html += F("Current size: "); html += size; - html += F(" characters (Max "); + html += F(" characters (Max "); html += RULES_MAX_SIZE; html += F(")"); addHtml(html); @@ -554,7 +554,6 @@ void Rule_showRuleTextArea(const String& fileName) { if (size > RULES_MAX_SIZE) { addHtml(F("Filesize exceeds web editor limit!")); } - addHtml(F("

")); } bool Rule_Download(const String& path) diff --git a/src/WebServer_SysVarPage.ino b/src/WebServer_SysVarPage.ino index 3cedb63d6..336277046 100644 --- a/src/WebServer_SysVarPage.ino +++ b/src/WebServer_SysVarPage.ino @@ -1,9 +1,17 @@ #ifdef WEBSERVER_SYSVARS + +#include "src/Helpers/SystemVariables.h" + // ******************************************************************************** // Web Interface sysvars showing all system vars and their value. // ******************************************************************************** +void addSysVar_enum_html(SystemVariables::Enum enumval) { + addSysVar_html(SystemVariables::toString(enumval)); +} + + void handle_sysvars() { checkRAM(F("handle_sysvars")); @@ -23,52 +31,56 @@ void handle_sysvars() { html_table_header(F("URL encoded"), F("ESPEasy_System_Variables"), 0); addTableSeparator(F("Constants"), 3, 3); - addSysVar_html(F("%CR%")); - addSysVar_html(F("%LF%")); - addSysVar_html(F("%SP%")); - addSysVar_html(F("%R%")); - addSysVar_html(F("%N%")); + addSysVar_enum_html(SystemVariables::CR); + addSysVar_enum_html(SystemVariables::LF); + addSysVar_enum_html(SystemVariables::SPACE); + addSysVar_enum_html(SystemVariables::S_CR); + addSysVar_enum_html(SystemVariables::S_LF); addTableSeparator(F("Network"), 3, 3); - addSysVar_html(F("%mac%")); + addSysVar_enum_html(SystemVariables::MAC); #if defined(ESP8266) - addSysVar_html(F("%mac_int%")); + addSysVar_enum_html(SystemVariables::MAC_INT); #endif // if defined(ESP8266) - addSysVar_html(F("%ip4%")); - addSysVar_html(F("%ip%")); - addSysVar_html(F("%rssi%")); - addSysVar_html(F("%ssid%")); - addSysVar_html(F("%bssid%")); - addSysVar_html(F("%wi_ch%")); + addSysVar_enum_html(SystemVariables::IP4); + addSysVar_enum_html(SystemVariables::IP4); + addSysVar_enum_html(SystemVariables::RSSI); + addSysVar_enum_html(SystemVariables::SSID); + addSysVar_enum_html(SystemVariables::BSSID); + addSysVar_enum_html(SystemVariables::WI_CH); addTableSeparator(F("System"), 3, 3); - addSysVar_html(F("%unit%")); - addSysVar_html(F("%sysload%")); - addSysVar_html(F("%sysheap%")); - addSysVar_html(F("%sysstack%")); - addSysVar_html(F("%sysname%")); + addSysVar_enum_html(SystemVariables::UNIT_sysvar); + addSysVar_enum_html(SystemVariables::SYSLOAD); + addSysVar_enum_html(SystemVariables::SYSHEAP); + addSysVar_enum_html(SystemVariables::SYSSTACK); + addSysVar_enum_html(SystemVariables::SYSNAME); #if FEATURE_ADC_VCC - addSysVar_html(F("%vcc%")); + addSysVar_enum_html(SystemVariables::VCC); #endif // if FEATURE_ADC_VCC addTableSeparator(F("System status"), 3, 3); - addSysVar_html(F("%iswifi%")); - addSysVar_html(F("%isntp%")); - addSysVar_html(F("%ismqtt%")); + addSysVar_enum_html(SystemVariables::ISWIFI); + addSysVar_enum_html(SystemVariables::ISNTP); + addSysVar_enum_html(SystemVariables::ISMQTT); #ifdef USES_P037 - addSysVar_html(F("%ismqttimp%")); + addSysVar_enum_html(SystemVariables::ISMQTTIMP); #endif // USES_P037 addTableSeparator(F("Time"), 3, 3); - addSysVar_html(F("%lcltime%")); - addSysVar_html(F("%lcltime_am%")); - addSysVar_html(F("%systm_hm%")); - addSysVar_html(F("%systm_hm_am%")); - addSysVar_html(F("%systime%")); - addSysVar_html(F("%systime_am%")); - addSysVar_html(F("%sysbuild_date%")); - addSysVar_html(F("%sysbuild_time%")); + addSysVar_enum_html(SystemVariables::LCLTIME); + addSysVar_enum_html(SystemVariables::LCLTIME_AM); + addSysVar_enum_html(SystemVariables::SYSTM_HM); + addSysVar_enum_html(SystemVariables::SYSTM_HM_AM); + addSysVar_enum_html(SystemVariables::SYSTIME); + addSysVar_enum_html(SystemVariables::SYSTIME_AM); + addSysVar_enum_html(SystemVariables::SYSBUILD_DATE); + addSysVar_enum_html(SystemVariables::SYSBUILD_TIME); + addSysVar_enum_html(SystemVariables::SYSBUILD_FILENAME); + addSysVar_enum_html(SystemVariables::SYSBUILD_DESCR); + addSysVar_enum_html(SystemVariables::SYSBUILD_GIT); + addTableSeparator(F("System"), 3, 3); addSysVar_html(F("%sysyear% // %sysyear_0%")); addSysVar_html(F("%sysyears%")); @@ -77,14 +89,14 @@ void handle_sysvars() { addSysVar_html(F("%syshour% // %syshour_0%")); addSysVar_html(F("%sysmin% // %sysmin_0%")); addSysVar_html(F("%syssec% // %syssec_0%")); - addSysVar_html(F("%syssec_d%")); - addSysVar_html(F("%sysweekday%")); - addSysVar_html(F("%sysweekday_s%")); + addSysVar_enum_html(SystemVariables::SYSSEC_D); + addSysVar_enum_html(SystemVariables::SYSWEEKDAY); + addSysVar_enum_html(SystemVariables::SYSWEEKDAY_S); addTableSeparator(F("System"), 3, 3); - addSysVar_html(F("%uptime%")); - addSysVar_html(F("%unixtime%")); - addSysVar_html(F("%unixday%")); - addSysVar_html(F("%unixday_sec%")); + addSysVar_enum_html(SystemVariables::UPTIME); + addSysVar_enum_html(SystemVariables::UNIXTIME); + addSysVar_enum_html(SystemVariables::UNIXDAY); + addSysVar_enum_html(SystemVariables::UNIXDAY_SEC); addSysVar_html(F("%sunset%")); addSysVar_html(F("%sunset-1h%")); addSysVar_html(F("%sunrise%")); @@ -172,6 +184,7 @@ void handle_sysvars() { TXBuffer.endStream(); } + void addSysVar_html(const String& input) { html_TR_TD(); { diff --git a/src/WebServer_ToolsPage.ino b/src/WebServer_ToolsPage.ino index b4ed4edc3..0db6ee56c 100644 --- a/src/WebServer_ToolsPage.ino +++ b/src/WebServer_ToolsPage.ino @@ -18,10 +18,10 @@ void handle_tools() { addFormSubHeader(F("Command")); html_TR_TD(); - addHtml(F(""); - html_TD(); + html_TR_TD(); addSubmitButton(); addHelpButton(F("ESPEasy_Command_Reference")); html_TR_TD(); diff --git a/src/WebServer_fwddecl.h b/src/WebServer_fwddecl.h index 739dd7435..d883d1695 100644 --- a/src/WebServer_fwddecl.h +++ b/src/WebServer_fwddecl.h @@ -3,6 +3,7 @@ #include "limits.h" #include "StringProviderTypes.h" +#include "src/Helpers/SystemVariables.h" // ******************************************************************************** @@ -112,4 +113,4 @@ void addSelector(const String& id, boolean reloadonchange = false, bool enabled = true); -#endif // WEBSERVER_FWD_DECL_H \ No newline at end of file +#endif // WEBSERVER_FWD_DECL_H diff --git a/src/_C015.ino b/src/_C015.ino index 57b42c0da..b0d4a390b 100644 --- a/src/_C015.ino +++ b/src/_C015.ino @@ -341,7 +341,7 @@ String Command_Blynk_Set_c015(struct EventStruct *event, const char* Line){ return err; } - String data = parseString(Line, 3, true, false); + String data = parseString(Line, 3); if (data.length() == 0){ String err = F("Skip sending empty data to blynk vPin "); diff --git a/src/_P003_Pulse.ino b/src/_P003_Pulse.ino index 9f807a75d..67e6bd324 100644 --- a/src/_P003_Pulse.ino +++ b/src/_P003_Pulse.ino @@ -138,6 +138,39 @@ boolean Plugin_003(byte function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_INFO,log); pinMode(Settings.TaskDevicePin1[event->TaskIndex], INPUT_PULLUP); success = Plugin_003_pulseinit(Settings.TaskDevicePin1[event->TaskIndex], event->TaskIndex,Settings.TaskDevicePluginConfig[event->TaskIndex][2]); + + // Restore any value that may have been read from the RTC. + Plugin_003_pulseCounter[event->TaskIndex] = UserVar[event->BaseVarIndex]; + Plugin_003_pulseTotalCounter[event->TaskIndex] = UserVar[event->BaseVarIndex+1]; + Plugin_003_pulseTime[event->TaskIndex] = UserVar[event->BaseVarIndex+2]; + + switch (Settings.TaskDevicePluginConfig[event->TaskIndex][1]) + { + case 0: + { + Plugin_003_pulseCounter[event->TaskIndex] = UserVar[event->BaseVarIndex]; + break; + } + case 1: + { + Plugin_003_pulseCounter[event->TaskIndex] = UserVar[event->BaseVarIndex]; + Plugin_003_pulseTotalCounter[event->TaskIndex] = UserVar[event->BaseVarIndex+1]; + Plugin_003_pulseTime[event->TaskIndex] = UserVar[event->BaseVarIndex+2]; + break; + } + case 2: + { + Plugin_003_pulseTotalCounter[event->TaskIndex] = UserVar[event->BaseVarIndex]; + break; + } + case 3: + { + Plugin_003_pulseCounter[event->TaskIndex] = UserVar[event->BaseVarIndex]; + Plugin_003_pulseTotalCounter[event->TaskIndex] = UserVar[event->BaseVarIndex+1]; + break; + } + } + break; } @@ -188,7 +221,7 @@ boolean Plugin_003(byte function, struct EventStruct *event, String& string) if (command == F("resetpulsecounter")) { // Allow for an optional taskIndex parameter. When not given it will take the first task with this plugin. - const taskIndex_t taskIndex = parseCommandArgumentTaskIndex(string, 2); + const taskIndex_t taskIndex = parseCommandArgumentTaskIndex(string, 1); if (validTaskIndex(taskIndex)) { if (event->TaskIndex != taskIndex) { break; diff --git a/src/_P082_GPS.ino b/src/_P082_GPS.ino index e539e99e9..d072010f1 100644 --- a/src/_P082_GPS.ino +++ b/src/_P082_GPS.ino @@ -63,7 +63,7 @@ #define P082_QUERY4_DFLT P082_QUERY_SPD -// #define P082_SEND_GPS_TO_LOG +#define P082_SEND_GPS_TO_LOG struct P082_data_struct : public PluginTaskData_base { P082_data_struct() : gps(nullptr), P082_easySerial(nullptr) {} diff --git a/src/_P083_SGP30.ino b/src/_P083_SGP30.ino index 55c14205d..db2121498 100644 --- a/src/_P083_SGP30.ino +++ b/src/_P083_SGP30.ino @@ -38,7 +38,7 @@ boolean Plugin_083(byte function, struct EventStruct *event, String& string) { Device[++deviceCount].Number = PLUGIN_ID_083; Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = SENSOR_TYPE_SINGLE; + Device[deviceCount].VType = SENSOR_TYPE_DUAL; Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = false; Device[deviceCount].InverseLogicOption = false; diff --git a/src/_P087_SerialProxy.ino b/src/_P087_SerialProxy.ino index e08694a57..6f17221ee 100644 --- a/src/_P087_SerialProxy.ino +++ b/src/_P087_SerialProxy.ino @@ -8,9 +8,12 @@ // Allows to redirect data to a controller // -#include #include "_Plugin_Helper.h" +#include "src/PluginStructs/P087_data_struct.h" + +#include + #define PLUGIN_087 #define PLUGIN_ID_087 87 #define PLUGIN_NAME_087 "Communication - Serial Proxy [TESTING]" @@ -27,139 +30,6 @@ #define P087_DEFAULT_BAUDRATE 38400 -#define P87_Nlines 2 -#define P87_Nchars 64 - -#define P087_INITSTRING 0 -#define P087_EXITSTRING 1 - - -struct P087_data_struct : public PluginTaskData_base { - P087_data_struct() : P087_easySerial(nullptr) {} - - ~P087_data_struct() { - reset(); - } - - void reset() { - if (P087_easySerial != nullptr) { - delete P087_easySerial; - P087_easySerial = nullptr; - } - } - - bool init(const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate) { - if ((serial_rx < 0) && (serial_tx < 0)) { - return false; - } - reset(); - P087_easySerial = new ESPeasySerial(serial_rx, serial_tx); - - if (isInitialized()) { - P087_easySerial->begin(baudrate); - return true; - } - return false; - } - - bool isInitialized() const { - return P087_easySerial != nullptr; - } - - void sendString(const String& data) { - if (isInitialized()) { - if (data.length() > 0) { - P087_easySerial->write(data.c_str()); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Proxy: Sending: "); - log += data; - addLog(LOG_LEVEL_INFO, log); - } - } - } - } - - bool loop() { - if (!isInitialized()) { - return false; - } - bool fullSentenceReceived = false; - - if (P087_easySerial != nullptr) { - while (P087_easySerial->available() > 0 && !fullSentenceReceived) { - // Look for end marker - char c = P087_easySerial->read(); - - switch (c) { - case 13: - { - const size_t length = sentence_part.length(); - bool valid = length > 0; - - for (size_t i = 0; i < length && valid; ++i) { - if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { - sentence_part = ""; - ++sentences_received_error; - valid = false; - } - } - - if (valid) { - fullSentenceReceived = true; - } - break; - } - case 10: - - // Ignore LF - break; - default: - sentence_part += c; - break; - } - - if (max_length_reached()) { fullSentenceReceived = true; } - } - } - - if (fullSentenceReceived) { - ++sentences_received; - length_last_received = sentence_part.length(); - } - return fullSentenceReceived; - } - - void getSentence(String& string) { - string = sentence_part; - sentence_part = ""; - } - - void getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const { - succes = sentences_received; - error = sentences_received_error; - length_last = length_last_received; - } - - void setMaxLength(uint16_t maxlenght) { - max_length = maxlenght; - } - -private: - - bool max_length_reached() const { - if (max_length == 0) { return false; } - return sentence_part.length() >= max_length; - } - - ESPeasySerial *P087_easySerial = nullptr; - String sentence_part; - uint16_t max_length = 550; - uint32_t sentences_received = 0; - uint32_t sentences_received_error = 0; - uint32_t length_last_received = 0; -}; - // Plugin settings: // Validate: @@ -259,49 +129,13 @@ boolean Plugin_087(byte function, struct EventStruct *event, String& string) { case PLUGIN_WEBFORM_LOAD: { serialHelper_webformLoad(event); - - /* - P087_data_struct *P087_data = - static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P087_data && P087_data->isInitialized()) { - String detectedString = F("Detected: "); - detectedString += String(P087_data->P087_easySerial->baudRate()); - addUnit(detectedString); - */ - addFormNumericBox(F("Baudrate"), P087_BAUDRATE_LABEL, P087_BAUDRATE, 2400, 115200); addUnit(F("baud")); - /* - { - // In a separate scope to free memory of String array as soon as possible - sensorTypeHelper_webformLoad_header(); - String options[P087_NR_OUTPUT_OPTIONS]; - - for (int i = 0; i < P087_NR_OUTPUT_OPTIONS; ++i) { - options[i] = Plugin_087_valuename(i, true); - } - - for (byte i = 0; i < P087_NR_OUTPUT_VALUES; ++i) { - const byte pconfigIndex = i + P087_QUERY1_CONFIG_POS; - sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P087_NR_OUTPUT_OPTIONS, options); - } - } - */ - - { - String strings[P87_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P87_Nlines, P87_Nchars); - - for (byte varNr = 0; varNr < P87_Nlines; varNr++) - { - String label = F("Init "); - label += String(varNr + 1); - addFormTextBox(label, getPluginCustomArgName(varNr), strings[varNr], P87_Nchars); - } - } - + addFormSubHeader(F("Filtering")); + P087_html_show_matchForms(event); + addFormSubHeader(F("Statistics")); P087_html_show_stats(event); success = true; @@ -312,22 +146,19 @@ boolean Plugin_087(byte function, struct EventStruct *event, String& string) { serialHelper_webformSave(event); P087_BAUDRATE = getFormItemInt(P087_BAUDRATE_LABEL); - String error; - char P087_deviceTemplate[P87_Nlines][P87_Nchars]; + P087_data_struct *P087_data = + static_cast(getPluginTaskData(event->TaskIndex)); - for (byte varNr = 0; varNr < P87_Nlines; varNr++) - { - if (!safe_strncpy(P087_deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr)), P87_Nchars)) { - error += getCustomTaskSettingsError(varNr); + if (nullptr != P087_data) { + for (byte varNr = 0; varNr < P87_Nlines; varNr++) + { + P087_data->setLine(varNr, web_server.arg(getPluginCustomArgName(varNr))); } + + addHtmlError(SaveCustomTaskSettings(event->TaskIndex, P087_data->_lines, P87_Nlines, 0)); + success = true; } - if (error.length() > 0) { - addHtmlError(error); - } - SaveCustomTaskSettings(event->TaskIndex, (byte *)&P087_deviceTemplate, sizeof(P087_deviceTemplate)); - - success = true; break; } @@ -343,6 +174,8 @@ boolean Plugin_087(byte function, struct EventStruct *event, String& string) { } if (P087_data->init(serial_rx, serial_tx, P087_BAUDRATE)) { + LoadCustomTaskSettings(event->TaskIndex, P087_data->_lines, P87_Nlines, 0); + P087_data->post_init(); success = true; if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { @@ -370,11 +203,9 @@ boolean Plugin_087(byte function, struct EventStruct *event, String& string) { static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P087_data) && P087_data->loop()) { - // schedule_task_device_timer(event->TaskIndex, millis() + 10); + schedule_task_device_timer(event->TaskIndex, millis() + 10); delay(0); // Processing a full sentence may take a while, run some // background tasks. - P087_data->getSentence(event->String2); - sendData(event); } success = true; } @@ -384,19 +215,65 @@ boolean Plugin_087(byte function, struct EventStruct *event, String& string) { case PLUGIN_READ: { P087_data_struct *P087_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P087_data)) { - String strings[P87_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P87_Nlines, P87_Nchars); - parseSystemVariables(strings[0], false); - P087_data->sendString(strings[0]); + if ((nullptr != P087_data) && P087_data->getSentence(event->String2)) { + if (Plugin_087_match_all(event->TaskIndex, event->String2)) { +// sendData(event); + addLog(LOG_LEVEL_DEBUG, event->String2); + success = true; + } } + + if ((nullptr != P087_data)) {} + break; + } + + case PLUGIN_WRITE: { + String cmd = parseString(string, 1); + + + if (cmd.equalsIgnoreCase(F("serialproxy_write"))) { + P087_data_struct *P087_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P087_data)) { + String param1 = parseStringKeepCase(string, 2); + parseSystemVariables(param1, false); + P087_data->sendString(param1); + addLog(LOG_LEVEL_INFO, param1); + success = true; + } + } + break; } } return success; } +bool Plugin_087_match_all(taskIndex_t taskIndex, String& received) +{ + P087_data_struct *P087_data = + static_cast(getPluginTaskData(taskIndex)); + + if ((nullptr == P087_data)) { + return false; + } + + + if (P087_data->disableFilterWindowActive()) { + addLog(LOG_LEVEL_INFO, F("Serial Proxy: Disable Filter Window active")); + return true; + } + + bool res = P087_data->matchRegexp(received); + + if (P087_data->invertMatch()) { + addLog(LOG_LEVEL_INFO, F("Serial Proxy: invert filter")); + return !res; + } + return res; +} + String Plugin_087_valuename(byte value_nr, bool displayString) { switch (value_nr) { case P087_QUERY_VALUE: return displayString ? F("Value") : F("v"); @@ -404,6 +281,84 @@ String Plugin_087_valuename(byte value_nr, bool displayString) { return ""; } +void P087_html_show_matchForms(struct EventStruct *event) { + P087_data_struct *P087_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P087_data)) { + addFormTextBox(F("RegEx"), getPluginCustomArgName(P087_REGEX_POS), P087_data->getRegEx(), P87_Nchars); + addFormNote(F("Captures are specified using round brackets.")); + + addFormNumericBox(F("Nr Chars use in regex"), getPluginCustomArgName(P087_NR_CHAR_USE_POS), P087_data->getRegExpMatchLength(), 0, 1024); + addFormNote(F("0 = Use all of the received string.")); + + addFormNumericBox(F("Filter Off Window after send"), + getPluginCustomArgName(P087_FILTER_OFF_WINDOW_POS), + P087_data->getFilterOffWindowTime(), + 0, + 60000); + addUnit(F("msec")); + addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); + + { + String options[P087_Match_Type_NR_ELEMENTS]; + int optionValues[P087_Match_Type_NR_ELEMENTS]; + + for (int i = 0; i < P087_Match_Type_NR_ELEMENTS; ++i) { + P087_Match_Type matchType = static_cast(i); + options[i] = P087_data_struct::MatchType_toString(matchType); + optionValues[i] = matchType; + } + P087_Match_Type choice = P087_data->getMatchType(); + addFormSelector(F("Match Type"), getPluginCustomArgName(P087_MATCH_TYPE_POS), P087_Match_Type_NR_ELEMENTS, options, optionValues, choice, false); + addFormNote(F("Capture filter can only be used on Global Match")); + } + + + byte lineNr = 0; + uint8_t capture = 0; + P087_Filter_Comp comparator = P087_Filter_Comp::Equal; + String filter; + + for (byte varNr = P087_FIRST_FILTER_POS; varNr < P87_Nlines; ++varNr) + { + String id = getPluginCustomArgName(varNr); + + switch (varNr % 3) { + case 0: + { + // Label + first parameter + filter = P087_data->getFilter(lineNr, capture, comparator); + ++lineNr; + String label; + label = F("Capture Filter "); + label += String(lineNr); + addRowLabel_tr_id(label, id); + + addNumericBox(id, capture, -1, P87_MAX_CAPTURE_INDEX); + break; + } + case 1: + { + // Comparator + String options[2]; + options[P087_Filter_Comp::Equal] = F("=="); + options[P087_Filter_Comp::NotEqual] = F("!="); + int optionValues[2] = { P087_Filter_Comp::Equal, P087_Filter_Comp::NotEqual }; + addSelector(id, 2, options, optionValues, NULL, comparator, false, ""); + break; + } + case 2: + { + // Compare with + addTextBox(id, filter, 32, false, false, "", ""); + break; + } + } + } + } +} + void P087_html_show_stats(struct EventStruct *event) { P087_data_struct *P087_data = static_cast(getPluginTaskData(event->TaskIndex)); diff --git a/src/_P091_SerSwitch.ino b/src/_P091_SerSwitch.ino index f7ba23252..12aafcb6f 100644 --- a/src/_P091_SerSwitch.ino +++ b/src/_P091_SerSwitch.ino @@ -622,8 +622,6 @@ boolean Plugin_091(byte function, struct EventStruct *event, String& string) log += F(":"); log += rcmd; addLog(LOG_LEVEL_INFO, log); - log = F("Ok"); - SendStatus(event->Source, log); } if ( command == F("relaypulse") ) @@ -674,8 +672,6 @@ boolean Plugin_091(byte function, struct EventStruct *event, String& string) log += String(event->Par3); log += F(" mS"); addLog(LOG_LEVEL_INFO, log); - log = F("\nOk"); - SendStatus(event->Source, log); } if ( command == F("relaylongpulse") ) @@ -727,8 +723,6 @@ boolean Plugin_091(byte function, struct EventStruct *event, String& string) log += String(event->Par3); log += F(" sec"); addLog(LOG_LEVEL_INFO, log); - log = F("\nOk"); - SendStatus(event->Source, log); } if ( command == F("ydim") ) // deal with dimmer command { @@ -756,11 +750,10 @@ boolean Plugin_091(byte function, struct EventStruct *event, String& string) } log += event->Par1; addLog(LOG_LEVEL_INFO, log); - log = F("\nOk"); } else { - log = F("\nNot supported"); + log = F("\nYDim not supported"); + SendStatus(event->Source, log); } - SendStatus(event->Source, log); } } diff --git a/src/src/DataStructs/ESPEasyDefaults.h b/src/src/DataStructs/ESPEasyDefaults.h index 26431df32..b1e9ed9f4 100644 --- a/src/src/DataStructs/ESPEasyDefaults.h +++ b/src/src/DataStructs/ESPEasyDefaults.h @@ -153,6 +153,9 @@ #ifndef DEFAULT_PIN_I2C_SCL #define DEFAULT_PIN_I2C_SCL 5 #endif +#ifndef DEFAULT_I2C_CLOCK_SPEED +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips +#endif #ifndef DEFAULT_PIN_STATUS_LED #define DEFAULT_PIN_STATUS_LED (-1) diff --git a/src/src/DataStructs/SettingsStruct.cpp b/src/src/DataStructs/SettingsStruct.cpp index 96da2a3ba..1cab7e24d 100644 --- a/src/src/DataStructs/SettingsStruct.cpp +++ b/src/src/DataStructs/SettingsStruct.cpp @@ -123,6 +123,7 @@ void SettingsStruct_tmpl::validate() { if (VariousBits1 > (1 << 30)) { VariousBits1 = 0; } ZERO_TERMINATE(Name); ZERO_TERMINATE(NTPHost); + if (I2C_clockSpeed == 0 || I2C_clockSpeed > 3400000) { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } } template @@ -215,6 +216,7 @@ void SettingsStruct_tmpl::clearMisc() { UseSerial = true; UseSSDP = false; WireClockStretchLimit = 0; + I2C_clockSpeed = 400000; GlobalSync = false; ConnectionFailuresThreshold = 0; MQTTRetainFlag = false; diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index 7d7c24a43..ab53d89ca 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -169,6 +169,7 @@ class SettingsStruct_tmpl float Longitude; uint32_t VariousBits1; uint32_t ResetFactoryDefaultPreference; // Do not clear this one in the clearAll() + uint32_t I2C_clockSpeed; // FIXME @TD-er: As discussed in #1292, the CRC for the settings is now disabled. // make sure crc is the last value in the struct diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp index ff9984ecc..2a8fa647c 100644 --- a/src/src/Helpers/SystemVariables.cpp +++ b/src/src/Helpers/SystemVariables.cpp @@ -7,8 +7,8 @@ #ifdef USES_MQTT -#include "../Globals/MQTT.h" -#endif +# include "../Globals/MQTT.h" +#endif // ifdef USES_MQTT String getReplacementString(const String& format, String& s) { @@ -63,45 +63,50 @@ void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) } SystemVariables::Enum enumval = static_cast(0); + do { enumval = SystemVariables::nextReplacementEnum(s, enumval); String value; - switch (enumval) + + switch (enumval) { case BSSID: value = String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("00:00:00:00:00:00") : WiFi.BSSIDstr()); break; case CR: value = "\r"; break; case IP: value = getValue(LabelType::IP_ADDRESS); break; - case IP4: value = WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.') + 1); break; // 4th IP octet + case IP4: value = String( (int) WiFi.localIP()[3] ); break; // 4th IP octet #ifdef USES_MQTT case ISMQTT: value = String(MQTTclient_connected); break; - #else + #else // ifdef USES_MQTT case ISMQTT: value = "0"; break; #endif // ifdef USES_MQTT #ifdef USES_P037 case ISMQTTIMP: value = String(P037_MQTTImport_connected); break; - #else + #else // ifdef USES_P037 case ISMQTTIMP: value = "0"; break; #endif // USES_P037 case ISNTP: value = String(statusNTPInitialized); break; - case ISWIFI: value = String(wifiStatus); break; // 0=disconnected, 1=connected, 2=got ip, 3=services initialized + case ISWIFI: value = String(wifiStatus); break; // 0=disconnected, 1=connected, 2=got ip, 3=services initialized case LCLTIME: value = getValue(LabelType::LOCAL_TIME); break; case LCLTIME_AM: value = node_time.getDateTimeString_ampm('-', ':', ' '); break; case LF: value = "\n"; break; case MAC: value = getValue(LabelType::STA_MAC); break; #ifdef ESP8266 - case MAC_INT: value = String(ESP.getChipId()); break; // Last 24 bit of MAC address as integer, to be used in rules. - #else - case MAC_INT: value = ""; break; // FIXME TD-er: Must find proper altrnative for ESP32. - #endif + case MAC_INT: value = String(ESP.getChipId()); break; // Last 24 bit of MAC address as integer, to be used in rules. + #else // ifdef ESP8266 + case MAC_INT: value = ""; break; // FIXME TD-er: Must find proper altrnative for ESP32. + #endif // ifdef ESP8266 case RSSI: value = getValue(LabelType::WIFI_RSSI); break; case SPACE: value = " "; break; case SSID: value = (wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("--") : WiFi.SSID(); break; case SUNRISE: SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); break; case SUNSET: SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); break; case SYSBUILD_DATE: value = String(CRCValues.compileDate); break; + case SYSBUILD_DESCR: value = getValue(LabelType::BUILD_DESC); break; + case SYSBUILD_FILENAME: value = getValue(LabelType::BINARY_FILENAME); break; + case SYSBUILD_GIT: value = getValue(LabelType::GIT_BUILD); break; case SYSBUILD_TIME: value = String(CRCValues.compileTime); break; case SYSDAY: value = String(node_time.day()); break; case SYSDAY_0: value = timeReplacement_leadZero(node_time.day()); break; @@ -136,23 +141,25 @@ void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) case UPTIME: value = String(wdcounter / 2); break; #if FEATURE_ADC_VCC case VCC: value = String(vcc); break; - #else + #else // if FEATURE_ADC_VCC case VCC: value = String(-1); break; #endif // if FEATURE_ADC_VCC case WI_CH: value = String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? 0 : WiFi.channel()); break; case UNKNOWN: - break; + break; } - switch(enumval) + switch (enumval) { case SUNRISE: case SUNSET: case UNKNOWN: + // Do not replace break; default: + if (useURLencode) { value = URLEncode(value.c_str()); } @@ -167,6 +174,7 @@ void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) if ((v_index != -1) && isDigit(s[v_index + 2])) { for (byte i = 0; i < CUSTOM_VARS_MAX; ++i) { String key = "%v" + String(i + 1) + '%'; + if (s.indexOf(key) != -1) { String value = String(customFloatVar[i]); @@ -191,23 +199,28 @@ SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, Sy } SystemVariables::Enum nextTested = static_cast(0); + if (last_tested > nextTested) { nextTested = static_cast(last_tested + 1); } + if (nextTested >= Enum::UNKNOWN) { return Enum::UNKNOWN; } - String str_prefix = SystemVariables::toString(nextTested).substring(0,2); - bool str_prefix_exists = str.indexOf(str_prefix) != -1; + String str_prefix = SystemVariables::toString(nextTested).substring(0, 2); + bool str_prefix_exists = str.indexOf(str_prefix) != -1; + for (int i = nextTested; i < Enum::UNKNOWN; ++i) { SystemVariables::Enum enumval = static_cast(i); - String new_str_prefix = SystemVariables::toString(enumval).substring(0,2); - if (str_prefix == new_str_prefix && !str_prefix_exists) { + String new_str_prefix = SystemVariables::toString(enumval).substring(0, 2); + + if ((str_prefix == new_str_prefix) && !str_prefix_exists) { // Just continue } else { - str_prefix = new_str_prefix; + str_prefix = new_str_prefix; str_prefix_exists = str.indexOf(str_prefix) != -1; + if (str_prefix_exists) { if (str.indexOf(SystemVariables::toString(enumval)) != -1) { return enumval; @@ -219,8 +232,6 @@ SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, Sy return Enum::UNKNOWN; } - - String SystemVariables::toString(SystemVariables::Enum enumval) { switch (enumval) { @@ -243,6 +254,9 @@ String SystemVariables::toString(SystemVariables::Enum enumval) case Enum::SUNRISE: return F("%sunrise"); case Enum::SUNSET: return F("%sunset"); case Enum::SYSBUILD_DATE: return F("%sysbuild_date%"); + case Enum::SYSBUILD_DESCR: return F("%sysbuild_desc%"); + case Enum::SYSBUILD_FILENAME: return F("%sysbuild_filename%"); + case Enum::SYSBUILD_GIT: return F("%sysbuild_git%"); case Enum::SYSBUILD_TIME: return F("%sysbuild_time%"); case Enum::SYSDAY: return F("%sysday%"); case Enum::SYSDAY_0: return F("%sysday_0%"); @@ -280,4 +294,4 @@ String SystemVariables::toString(SystemVariables::Enum enumval) case Enum::UNKNOWN: break; } return F("Unknown"); -} \ No newline at end of file +} diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h index c82ceb918..67421791b 100644 --- a/src/src/Helpers/SystemVariables.h +++ b/src/src/Helpers/SystemVariables.h @@ -28,6 +28,9 @@ public: SUNRISE, SUNSET, SYSBUILD_DATE, + SYSBUILD_DESCR, + SYSBUILD_FILENAME, + SYSBUILD_GIT, SYSBUILD_TIME, SYSDAY, SYSDAY_0, diff --git a/src/src/PluginStructs/P087_data_struct.cpp b/src/src/PluginStructs/P087_data_struct.cpp new file mode 100644 index 000000000..c44f7ec5f --- /dev/null +++ b/src/src/PluginStructs/P087_data_struct.cpp @@ -0,0 +1,356 @@ +#include "P087_data_struct.h" + + +#ifdef USES_P087 + + +P087_data_struct::P087_data_struct() : easySerial(nullptr) {} + +P087_data_struct::~P087_data_struct() { + reset(); +} + +void P087_data_struct::reset() { + if (easySerial != nullptr) { + delete easySerial; + easySerial = nullptr; + } +} + +bool P087_data_struct::init(const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate) { + if ((serial_rx < 0) && (serial_tx < 0)) { + return false; + } + reset(); + easySerial = new ESPeasySerial(serial_rx, serial_tx); + + if (isInitialized()) { + easySerial->begin(baudrate); + return true; + } + return false; +} + +void P087_data_struct::post_init() { + for (uint8_t i = 0; i < P87_MAX_CAPTURE_INDEX; ++i) { + capture_index_used[i] = false; + } + regex_empty = _lines[P087_REGEX_POS].length() == 0; + String log = F("P087_post_init:"); + + for (uint8_t i = 0; i < P087_NR_FILTERS; ++i) { + // Create some quick lookup table to see if we have a filter for the specific index + capture_index_must_not_match[i] = _lines[i * 3 + P087_FIRST_FILTER_POS + 1].toInt() == P087_Filter_Comp::NotEqual; + int index = _lines[i * 3 + P087_FIRST_FILTER_POS].toInt(); + + // Index is negative when not used. + if ((index >= 0) && (index < P87_MAX_CAPTURE_INDEX) && (_lines[i * 3 + P087_FIRST_FILTER_POS + 2].length() > 0)) { + log += ' '; + log += String(i); + log += ':'; + log += String(index); + capture_index[i] = index; + capture_index_used[index] = true; + } + } + addLog(LOG_LEVEL_DEBUG, log); +} + +bool P087_data_struct::isInitialized() const { + return easySerial != nullptr; +} + +void P087_data_struct::sendString(const String& data) { + if (isInitialized()) { + if (data.length() > 0) { + setDisableFilterWindowTimer(); + easySerial->write(data.c_str()); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("Proxy: Sending: "); + log += data; + addLog(LOG_LEVEL_INFO, log); + } + } + } +} + +bool P087_data_struct::loop() { + if (!isInitialized()) { + return false; + } + bool fullSentenceReceived = false; + + if (easySerial != nullptr) { + int available = easySerial->available(); + + while (available > 0 && !fullSentenceReceived) { + // Look for end marker + char c = easySerial->read(); + --available; + + if (available == 0) { + available = easySerial->available(); + delay(0); + } + + switch (c) { + case 13: + { + const size_t length = sentence_part.length(); + bool valid = length > 0; + + for (size_t i = 0; i < length && valid; ++i) { + if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) { + sentence_part = ""; + ++sentences_received_error; + valid = false; + } + } + + if (valid) { + fullSentenceReceived = true; + last_sentence = sentence_part; + sentence_part = ""; + } + break; + } + case 10: + + // Ignore LF + break; + default: + sentence_part += c; + break; + } + + if (max_length_reached()) { fullSentenceReceived = true; } + } + } + + if (fullSentenceReceived) { + ++sentences_received; + length_last_received = last_sentence.length(); + } + return fullSentenceReceived; +} + +bool P087_data_struct::getSentence(String& string) { + string = last_sentence; + if (string.length() == 0) { + return false; + } + last_sentence = ""; + return true; +} + +void P087_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const { + succes = sentences_received; + error = sentences_received_error; + length_last = length_last_received; +} + +void P087_data_struct::setMaxLength(uint16_t maxlenght) { + max_length = maxlenght; +} + +void P087_data_struct::setLine(byte varNr, const String& line) { + if (varNr < P87_Nlines) { + _lines[varNr] = line; + } +} + +String P087_data_struct::getRegEx() const { + return _lines[P087_REGEX_POS]; +} + +uint16_t P087_data_struct::getRegExpMatchLength() const { + return _lines[P087_NR_CHAR_USE_POS].toInt(); +} + +uint32_t P087_data_struct::getFilterOffWindowTime() const { + return _lines[P087_FILTER_OFF_WINDOW_POS].toInt(); +} + +P087_Match_Type P087_data_struct::getMatchType() const { + return static_cast(_lines[P087_MATCH_TYPE_POS].toInt()); +} + +bool P087_data_struct::invertMatch() const { + switch (getMatchType()) { + case Regular_Match: // fallthrough + case Global_Match: + break; + case Regular_Match_inverted: // fallthrough + case Global_Match_inverted: + return true; + case Filter_Disabled: + break; + } + return false; +} + +bool P087_data_struct::globalMatch() const { + switch (getMatchType()) { + case Regular_Match: // fallthrough + case Regular_Match_inverted: + break; + case Global_Match: // fallthrough + case Global_Match_inverted: + return true; + case Filter_Disabled: + break; + } + return false; +} + +String P087_data_struct::getFilter(uint8_t lineNr, uint8_t& capture, P087_Filter_Comp& comparator) const +{ + uint8_t varNr = lineNr * 3 + P087_FIRST_FILTER_POS; + + if (varNr >= P87_Nlines) { return ""; } + + capture = _lines[varNr++].toInt(); + comparator = _lines[varNr++] == "1" ? P087_Filter_Comp::NotEqual : P087_Filter_Comp::Equal; + return _lines[varNr]; +} + +void P087_data_struct::setDisableFilterWindowTimer() { + if (getFilterOffWindowTime() == 0) { + disable_filter_window = 0; + } + else { + disable_filter_window = millis() + getFilterOffWindowTime(); + } +} + +bool P087_data_struct::disableFilterWindowActive() const { + if (disable_filter_window != 0) { + if (!timeOutReached(disable_filter_window)) { + // We're still in the window where filtering is disabled + return true; + } + } + return false; +} + +typedef std::pair capture_tuple; +static std::vector capture_vector; + + +// called for each match +void P087_data_struct::match_callback(const char *match, const unsigned int length, const MatchState& ms) +{ + for (byte i = 0; i < ms.level; i++) + { + capture_tuple tuple; + tuple.first = i; + tuple.second = ms.GetCapture(i); + capture_vector.push_back(tuple); + } // end of for each capture +} + +bool P087_data_struct::matchRegexp(String& received) const { + size_t strlength = received.length(); + + if (strlength == 0) { + return false; + } + if (regex_empty || getMatchType() == Filter_Disabled) { + return true; + } + + + uint32_t regexp_match_length = getRegExpMatchLength(); + + if ((regexp_match_length > 0) && (strlength > regexp_match_length)) { + strlength = regexp_match_length; + } + + // We need to do a const_cast here, but this only is valid as long as we + // don't call a replace function from regexp. + MatchState ms(const_cast(received.c_str()), strlength); + + bool match_result = false; + if (globalMatch()) { + capture_vector.clear(); + ms.GlobalMatch(_lines[P087_REGEX_POS].c_str(), match_callback); + const uint8_t vectorlength = capture_vector.size(); + + for (uint8_t i = 0; i < vectorlength; ++i) { + if ((capture_vector[i].first < P87_MAX_CAPTURE_INDEX) && capture_index_used[capture_vector[i].first]) { + for (uint8_t n = 0; n < P087_NR_FILTERS; ++n) { + unsigned int lines_index = n * 3 + P087_FIRST_FILTER_POS + 2; + + if ((capture_index[n] == capture_vector[i].first) && (_lines[lines_index].length() != 0)) { + String log; + log.reserve(32); + log = F("P087: Index: "); + log += capture_vector[i].first; + log += F(" Found "); + log += capture_vector[i].second; + + // Found a Capture Filter with this capture index. + if (capture_vector[i].second == _lines[lines_index]) { + log += F(" Matches"); + + // Found a match. Now check if it is supposed to be one or not. + if (capture_index_must_not_match[n]) { + log += F(" (!=)"); + addLog(LOG_LEVEL_INFO, log); + return false; + } else { + match_result = true; + log += F(" (==)"); + } + } else { + log += F(" No Match"); + + if (capture_index_must_not_match[n]) { + log += F(" (!=) "); + } else { + log += F(" (==) "); + } + log += _lines[lines_index]; + } + addLog(LOG_LEVEL_INFO, log); + } + } + } + } + capture_vector.clear(); + } else { + char result = ms.Match(_lines[P087_REGEX_POS].c_str()); + + if (result == REGEXP_MATCHED) { + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Match at: "); + log += ms.MatchStart; + log += F(" Match Length: "); + log += ms.MatchLength; + addLog(LOG_LEVEL_DEBUG, log); + } + match_result = true; + } + } + return match_result; +} + +String P087_data_struct::MatchType_toString(P087_Match_Type matchType) { + switch (matchType) + { + case P087_Match_Type::Regular_Match: return F("Regular Match"); + case P087_Match_Type::Regular_Match_inverted: return F("Regular Match inverted"); + case P087_Match_Type::Global_Match: return F("Global Match"); + case P087_Match_Type::Global_Match_inverted: return F("Global Match inverted"); + case P087_Match_Type::Filter_Disabled: return F("Filter Disabled"); + } + return ""; +} + +bool P087_data_struct::max_length_reached() const { + if (max_length == 0) { return false; } + return sentence_part.length() >= max_length; +} + +#endif // USES_P087 diff --git a/src/src/PluginStructs/P087_data_struct.h b/src/src/PluginStructs/P087_data_struct.h new file mode 100644 index 000000000..6d3b0af0a --- /dev/null +++ b/src/src/PluginStructs/P087_data_struct.h @@ -0,0 +1,131 @@ +#ifndef PLUGINSTRUCTS_P087_DATA_STRUCT_H +#define PLUGINSTRUCTS_P087_DATA_STRUCT_H + +#include +#include "../../_Plugin_Helper.h" + +#ifdef USES_P087 +# include + + +# define P087_REGEX_POS 0 +# define P087_NR_CHAR_USE_POS 1 +# define P087_FILTER_OFF_WINDOW_POS 2 +# define P087_MATCH_TYPE_POS 3 + +# define P087_FIRST_FILTER_POS 6 + +# define P087_NR_FILTERS 10 +# define P87_Nlines (P087_FIRST_FILTER_POS + 3 * (P087_NR_FILTERS)) +# define P87_Nchars 128 +# define P87_MAX_CAPTURE_INDEX 32 + + +enum P087_Filter_Comp { + Equal = 0, + NotEqual = 1 +}; + +enum P087_Match_Type { + Regular_Match = 0, + Regular_Match_inverted = 1, + Global_Match = 2, + Global_Match_inverted = 3, + Filter_Disabled = 4 +}; +# define P087_Match_Type_NR_ELEMENTS 5 + + +struct P087_data_struct : public PluginTaskData_base { +public: + + P087_data_struct(); + + ~P087_data_struct(); + + void reset(); + + bool init(const int16_t serial_rx, + const int16_t serial_tx, + unsigned long baudrate); + + // Called after loading the config from the settings. + // Will interpret some data and load caches. + void post_init(); + + bool isInitialized() const; + + void sendString(const String& data); + + bool loop(); + + // Get the received sentence + // @retval true when the string is not empty. + bool getSentence(String& string); + + void getSentencesReceived(uint32_t& succes, + uint32_t& error, + uint32_t& length_last) const; + + void setMaxLength(uint16_t maxlenght); + + void setLine(byte varNr, + const String& line); + + String getRegEx() const; + + uint16_t getRegExpMatchLength() const; + + uint32_t getFilterOffWindowTime() const; + + P087_Match_Type getMatchType() const; + + bool invertMatch() const; + + bool globalMatch() const; + + String getFilter(uint8_t lineNr, + uint8_t & capture, + P087_Filter_Comp& comparator) const; + + void setDisableFilterWindowTimer(); + + bool disableFilterWindowActive() const; + + // called for each match when calling Global_Match + static void match_callback(const char *match, + const unsigned int length, + const MatchState & ms); + + bool matchRegexp(String& received) const; + + static String MatchType_toString(P087_Match_Type matchType); + + + // Made public so we don't have to copy the values when loading/saving. + String _lines[P87_Nlines]; + +private: + + bool max_length_reached() const; + + ESPeasySerial *easySerial = nullptr; + String sentence_part; + String last_sentence; + uint16_t max_length = 550; + uint32_t sentences_received = 0; + uint32_t sentences_received_error = 0; + uint32_t length_last_received = 0; + unsigned long disable_filter_window = 0; + + uint8_t capture_index[P87_MAX_CAPTURE_INDEX] = { 0 }; + + bool capture_index_used[P87_MAX_CAPTURE_INDEX]; + bool capture_index_must_not_match[P87_MAX_CAPTURE_INDEX]; + bool regex_empty = false; +}; + + +#endif // USES_P087 + +#endif // PLUGINSTRUCTS_P087_DATA_STRUCT_H diff --git a/src/src/Static/WebStaticData.h b/src/src/Static/WebStaticData.h index af3ebc789..4117c97e5 100644 --- a/src/src/Static/WebStaticData.h +++ b/src/src/Static/WebStaticData.h @@ -115,6 +115,19 @@ static const char jsClipboardCopyPart3[] PROGMEM = { "" }; + +static const char jsSaveRules[] PROGMEM = { + "function saveRulesFile(){document.getElementById(\"save_button\");" + "let e=document.getElementById(\"size\"),t=document.getElementById(\"rules\").value;" + "t=t.replace(/\\r?\\n/g,\"\\r\\n\");" + "let n=document.getElementById(\"set\").value,l=new File([t],\"rules\"+n+\".txt\",{type:\"text/plain\"}),a=new FormData;" + "a.append(\"file\",l),a.append(\"enctype\",\"multipart/form-data\");" + "let o=\"/rules\"+n+\".txt?callback=\"+Date.now();" + "fetch(o).then(e=>e.text()).then(l=>{if(t===l)console.log(\"nothing to save...\");" + "else{fetch(\"/upload\",{method:\"POST\",body:a}).then(e=>e.text()).then(l=>{let a=\"/rules\"+n+\".txt?callback=\"+Date.now();" + "fetch(a).then(e=>e.text()).then(n=>{t===n?(toasting(),e.innerHTML=t.length):console.log(\"error when saving...\")})})}})}" +}; + static const char DATA_UPDATE_SENSOR_VALUES_DEVICE_PAGE_JS[] PROGMEM = {0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x20,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x73,0x29,0x7b,0x76,0x61,0x72,0x20,0x61,0x2c,0x6c,0x2c,0x6f,0x3d,0x30,0x3b,0x69,0x73,0x4e,0x61,0x4e,0x28,0x73,0x29,0x26,0x26,0x28,0x73,0x3d,0x31,0x29,0x2c,0x6e,0x75,0x6c,0x6c,0x3d,0x3d,0x65,0x26,0x26,0x28,0x65,0x3d,0x31,0x65,0x33,0x29,0x3b,0x76,0x61,0x72,0x20,0x6e,0x3d,0x73,0x65,0x74,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x29,0x7b,0x6f,0x3e,0x30,0x3f,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x3a,0x2b,0x2b,0x73,0x3e,0x31,0x3f,0x6f,0x3d,0x31,0x3a,0x28,0x66,0x65,0x74,0x63,0x68,0x28,0x22,0x2f,0x6a,0x73,0x6f,0x6e,0x3f,0x76,0x69,0x65,0x77,0x3d,0x73,0x65,0x6e,0x73,0x6f,0x72,0x75,0x70,0x64,0x61,0x74,0x65,0x22,0x29,0x2e,0x74,0x68,0x65,0x6e,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x73,0x29,0x7b,0x76,0x61,0x72,0x20,0x6f,0x3b,0x32,0x30,0x30,0x3d,0x3d,0x3d,0x73,0x2e,0x73,0x74,0x61,0x74,0x75,0x73,0x3f,0x73,0x2e,0x6a,0x73,0x6f,0x6e,0x28,0x29,0x2e,0x74,0x68,0x65,0x6e,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x73,0x29,0x7b,0x66,0x6f,0x72,0x28,0x65,0x3d,0x73,0x2e,0x54,0x54,0x4c,0x2c,0x61,0x3d,0x30,0x3b,0x61,0x3c,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x2e,0x6c,0x65,0x6e,0x67,0x74,0x68,0x3b,0x61,0x2b,0x2b,0x29,0x69,0x66,0x28,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x68,0x61,0x73,0x4f,0x77,0x6e,0x50,0x72,0x6f,0x70,0x65,0x72,0x74,0x79,0x28,0x22,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x22,0x29,0x29,0x66,0x6f,0x72,0x28,0x6c,0x3d,0x30,0x3b,0x6c,0x3c,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x2e,0x6c,0x65,0x6e,0x67,0x74,0x68,0x3b,0x6c,0x2b,0x2b,0x29,0x74,0x72,0x79,0x7b,0x6f,0x3d,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x7d,0x63,0x61,0x74,0x63,0x68,0x28,0x65,0x29,0x7b,0x6f,0x3d,0x65,0x2e,0x6e,0x61,0x6d,0x65,0x7d,0x66,0x69,0x6e,0x61,0x6c,0x6c,0x79,0x7b,0x69,0x66,0x28,0x22,0x54,0x79,0x70,0x65,0x45,0x72,0x72,0x6f,0x72,0x22,0x21,0x3d,0x3d,0x6f,0x29,0x7b,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x3d,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x2c,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x56,0x61,0x6c,0x75,0x65,0x3d,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x4e,0x72,0x44,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x2c,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x3d,0x70,0x61,0x72,0x73,0x65,0x46,0x6c,0x6f,0x61,0x74,0x28,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x29,0x2e,0x74,0x6f,0x46,0x69,0x78,0x65,0x64,0x28,0x64,0x65,0x63,0x69,0x6d,0x61,0x6c,0x73,0x56,0x61,0x6c,0x75,0x65,0x29,0x3b,0x76,0x61,0x72,0x20,0x72,0x3d,0x22,0x76,0x61,0x6c,0x75,0x65,0x5f,0x22,0x2b,0x28,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2b,0x22,0x5f,0x22,0x2b,0x28,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2c,0x74,0x3d,0x22,0x76,0x61,0x6c,0x75,0x65,0x6e,0x61,0x6d,0x65,0x5f,0x22,0x2b,0x28,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2b,0x22,0x5f,0x22,0x2b,0x28,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x56,0x61,0x6c,0x75,0x65,0x4e,0x75,0x6d,0x62,0x65,0x72,0x2d,0x31,0x29,0x2c,0x75,0x3d,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x72,0x29,0x2c,0x63,0x3d,0x64,0x6f,0x63,0x75,0x6d,0x65,0x6e,0x74,0x2e,0x67,0x65,0x74,0x45,0x6c,0x65,0x6d,0x65,0x6e,0x74,0x42,0x79,0x49,0x64,0x28,0x74,0x29,0x3b,0x6e,0x75,0x6c,0x6c,0x21,0x3d,0x3d,0x75,0x26,0x26,0x28,0x75,0x2e,0x69,0x6e,0x6e,0x65,0x72,0x48,0x54,0x4d,0x4c,0x3d,0x74,0x65,0x6d,0x70,0x56,0x61,0x6c,0x75,0x65,0x29,0x2c,0x6e,0x75,0x6c,0x6c,0x21,0x3d,0x3d,0x63,0x26,0x26,0x28,0x63,0x2e,0x69,0x6e,0x6e,0x65,0x72,0x48,0x54,0x4d,0x4c,0x3d,0x73,0x2e,0x53,0x65,0x6e,0x73,0x6f,0x72,0x73,0x5b,0x61,0x5d,0x2e,0x54,0x61,0x73,0x6b,0x56,0x61,0x6c,0x75,0x65,0x73,0x5b,0x6c,0x5d,0x2e,0x4e,0x61,0x6d,0x65,0x2b,0x22,0x3a,0x22,0x29,0x7d,0x7d,0x65,0x3d,0x73,0x2e,0x54,0x54,0x4c,0x2c,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x2c,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x30,0x29,0x7d,0x29,0x3a,0x63,0x6f,0x6e,0x73,0x6f,0x6c,0x65,0x2e,0x6c,0x6f,0x67,0x28,0x22,0x4c,0x6f,0x6f,0x6b,0x73,0x20,0x6c,0x69,0x6b,0x65,0x20,0x74,0x68,0x65,0x72,0x65,0x20,0x77,0x61,0x73,0x20,0x61,0x20,0x70,0x72,0x6f,0x62,0x6c,0x65,0x6d,0x2e,0x20,0x53,0x74,0x61,0x74,0x75,0x73,0x20,0x43,0x6f,0x64,0x65,0x3a,0x20,0x22,0x2b,0x73,0x2e,0x73,0x74,0x61,0x74,0x75,0x73,0x29,0x7d,0x29,0x2e,0x63,0x61,0x74,0x63,0x68,0x28,0x66,0x75,0x6e,0x63,0x74,0x69,0x6f,0x6e,0x28,0x73,0x29,0x7b,0x63,0x6f,0x6e,0x73,0x6f,0x6c,0x65,0x2e,0x6c,0x6f,0x67,0x28,0x73,0x2e,0x6d,0x65,0x73,0x73,0x61,0x67,0x65,0x29,0x2c,0x65,0x3d,0x35,0x65,0x33,0x2c,0x63,0x6c,0x65,0x61,0x72,0x49,0x6e,0x74,0x65,0x72,0x76,0x61,0x6c,0x28,0x6e,0x29,0x2c,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x65,0x2c,0x30,0x29,0x7d,0x29,0x2c,0x6f,0x3d,0x31,0x29,0x7d,0x2c,0x65,0x29,0x7d,0x6c,0x6f,0x6f,0x70,0x44,0x65,0x4c,0x6f,0x6f,0x70,0x28,0x31,0x65,0x33,0x2c,0x30,0x29,0x3b, 0}; diff --git a/static/rules_save.js b/static/rules_save.js new file mode 100644 index 000000000..1b2ccb1fa --- /dev/null +++ b/static/rules_save.js @@ -0,0 +1,36 @@ +function saveRulesFile() { + let button = document.getElementById('save_button'); + let size = document.getElementById('size'); + let ruleTextNew = document.getElementById('rules').value; + ruleTextNew = ruleTextNew.replace(/\\r?\\n/g, '\\r\\n'); + let ruleNumber = document.getElementById('set').value; + let ruleTextFileData = new File([ruleTextNew], 'rules' + ruleNumber + '.txt', { + type: 'text/plain' + }); + let formData = new FormData(); + formData.append('file', ruleTextFileData); + formData.append('enctype', 'multipart/form-data'); + let url = '/rules' + ruleNumber + '.txt?callback=' + Date.now(); + fetch(url).then(res => res.text()).then((ruleTextOld) => { + if (ruleTextNew === ruleTextOld) { + console.log('nothing to save...'); + } else { + let url = '/upload'; + fetch(url, { + method: 'POST', + body: formData + }).then( + response => response.text()).then(html => { + let url = '/rules' + ruleNumber + '.txt?callback=' + Date.now(); + fetch(url).then(res => res.text()).then((ruleTextNewCheck) => { + if (ruleTextNew === ruleTextNewCheck) { + toasting(); + size.innerHTML = ruleTextNew.length; + } else { + console.log('error when saving...'); + } + }); + }); + } + }); +};