mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-07-28 04:07:47 +00:00
Merge pull request #2957 from TD-er/feature/serial_proxy_enhancements
Feature/serial proxy enhancements
This commit is contained in:
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
#include "src/Regexp.h"
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <Regexp.h>
|
||||
|
||||
// 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 () {}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <Regexp.h>
|
||||
|
||||
// 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 () {}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <Regexp.h>
|
||||
|
||||
// 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 () {}
|
||||
@@ -0,0 +1,29 @@
|
||||
#include <Regexp.h>
|
||||
|
||||
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 () {}
|
||||
@@ -0,0 +1,23 @@
|
||||
#include <Regexp.h>
|
||||
|
||||
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 () {}
|
||||
@@ -0,0 +1,9 @@
|
||||
MatchState KEYWORD1
|
||||
Match KEYWORD2
|
||||
Target KEYWORD2
|
||||
GetMatch KEYWORD2
|
||||
GetCapture KEYWORD2
|
||||
GetResult KEYWORD2
|
||||
MatchCount KEYWORD2
|
||||
GlobalMatch KEYWORD2
|
||||
GlobalReplace KEYWORD2
|
||||
@@ -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=*
|
||||
@@ -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 <Regexp.h>
|
||||
|
||||
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 <setjmp.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#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)<ms->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 (s<ms->src_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 = s<ms->src_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
|
||||
|
||||
@@ -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 <Arduino.h>
|
||||
|
||||
// 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;
|
||||
|
||||
+14
-12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,6 +369,8 @@ void setup()
|
||||
rulesProcessing(event); // TD-er: Process events in the setup() now.
|
||||
}
|
||||
|
||||
setWebserverRunning(true);
|
||||
|
||||
WiFiConnectRelaxed();
|
||||
|
||||
#ifdef FEATURE_REPORTING
|
||||
|
||||
+140
-20
@@ -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) {
|
||||
@@ -299,6 +299,7 @@ void afterloadSettings() {
|
||||
ResetFactoryDefaultPreference = Settings.ResetFactoryDefaultPreference;
|
||||
}
|
||||
msecTimerHandler.setEcoMode(Settings.EcoPowerMode());
|
||||
|
||||
if (!Settings.UseRules) {
|
||||
eventQueue.clear();
|
||||
}
|
||||
@@ -442,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.
|
||||
@@ -486,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;
|
||||
}
|
||||
@@ -566,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.
|
||||
}
|
||||
@@ -604,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 ");
|
||||
|
||||
@@ -635,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;
|
||||
@@ -664,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,
|
||||
@@ -761,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
|
||||
|
||||
@@ -789,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();
|
||||
@@ -823,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;
|
||||
@@ -836,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();
|
||||
@@ -965,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;
|
||||
|
||||
@@ -972,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) {
|
||||
|
||||
+3
-3
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
+3
-3
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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("<select class='wide' name='");
|
||||
html += F("<select class='");
|
||||
html += classname;
|
||||
html += F("' name='");
|
||||
html += id;
|
||||
html += F("' id='");
|
||||
html += id;
|
||||
@@ -311,12 +330,19 @@ void addFloatNumberBox(const String& id, float value, float min, float max)
|
||||
// Add Textbox
|
||||
// ********************************************************************************
|
||||
void addTextBox(const String& id, const String& value, int maxlength, bool readonly, bool required, const String& pattern)
|
||||
{
|
||||
addTextBox(id, value, maxlength, readonly, required, pattern, F("wide"));
|
||||
}
|
||||
|
||||
void addTextBox(const String& id, const String& value, int maxlength, bool readonly, bool required, const String& pattern, const String& classname)
|
||||
{
|
||||
String html;
|
||||
|
||||
html.reserve(96 + id.length() + value.length() + pattern.length());
|
||||
|
||||
html += F("<input class='wide' type='text' name='");
|
||||
html += F("<input class='");
|
||||
html += classname;
|
||||
html += F("' type='text' name='");
|
||||
html += id;
|
||||
html += F("' maxlength=");
|
||||
html += maxlength;
|
||||
|
||||
@@ -52,7 +52,7 @@ void handle_rules() {
|
||||
} else {
|
||||
// Save as soon as possible, as the webserver may already overwrite the args.
|
||||
const byte *memAddress = reinterpret_cast<const byte *>(web_server.arg(F("rules")).c_str());
|
||||
error = SaveToFile(fileName.c_str(), 0, memAddress, rulesLength, "w");
|
||||
error = doSaveToFile(fileName.c_str(), 0, memAddress, rulesLength, "w");
|
||||
}
|
||||
} else {
|
||||
error = F("Error: Data was not saved, rules argument missing or corrupted");
|
||||
|
||||
@@ -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();
|
||||
{
|
||||
|
||||
@@ -18,10 +18,10 @@ void handle_tools() {
|
||||
|
||||
addFormSubHeader(F("Command"));
|
||||
html_TR_TD();
|
||||
addHtml(F("<TR><TD style='width: 180px'><input class='wide' type='text' name='cmd' value='"));
|
||||
addHtml(F("<TR><TD colspan='2'><input style='width: 98%' type='text' name='cmd' value='"));
|
||||
addHtml(webrequest);
|
||||
addHtml("'>");
|
||||
html_TD();
|
||||
html_TR_TD();
|
||||
addSubmitButton();
|
||||
addHelpButton(F("ESPEasy_Command_Reference"));
|
||||
html_TR_TD();
|
||||
|
||||
+149
-194
@@ -8,9 +8,12 @@
|
||||
// Allows to redirect data to a controller
|
||||
//
|
||||
|
||||
#include <ESPeasySerial.h>
|
||||
#include "_Plugin_Helper.h"
|
||||
|
||||
#include "src/PluginStructs/P087_data_struct.h"
|
||||
|
||||
#include <Regexp.h>
|
||||
|
||||
#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<P087_data_struct *>(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<P087_data_struct *>(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<P087_data_struct *>(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<P087_data_struct *>(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<P087_data_struct *>(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<P087_data_struct *>(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<P087_data_struct *>(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<P087_Match_Type>(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<P087_data_struct *>(getPluginTaskData(event->TaskIndex));
|
||||
|
||||
@@ -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,51 @@ void SystemVariables::parseSystemVariables(String& s, boolean useURLencode)
|
||||
}
|
||||
|
||||
SystemVariables::Enum enumval = static_cast<SystemVariables::Enum>(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 = WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.') + 1); 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 +142,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 +175,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 +200,28 @@ SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, Sy
|
||||
}
|
||||
|
||||
SystemVariables::Enum nextTested = static_cast<SystemVariables::Enum>(0);
|
||||
|
||||
if (last_tested > nextTested) {
|
||||
nextTested = static_cast<SystemVariables::Enum>(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<SystemVariables::Enum>(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 +233,6 @@ SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, Sy
|
||||
return Enum::UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
|
||||
String SystemVariables::toString(SystemVariables::Enum enumval)
|
||||
{
|
||||
switch (enumval) {
|
||||
@@ -243,6 +255,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 +295,4 @@ String SystemVariables::toString(SystemVariables::Enum enumval)
|
||||
case Enum::UNKNOWN: break;
|
||||
}
|
||||
return F("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ public:
|
||||
SUNRISE,
|
||||
SUNSET,
|
||||
SYSBUILD_DATE,
|
||||
SYSBUILD_DESCR,
|
||||
SYSBUILD_FILENAME,
|
||||
SYSBUILD_GIT,
|
||||
SYSBUILD_TIME,
|
||||
SYSDAY,
|
||||
SYSDAY_0,
|
||||
|
||||
@@ -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<P087_Match_Type>(_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<uint8_t, String> capture_tuple;
|
||||
static std::vector<capture_tuple> 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<char *>(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
|
||||
@@ -0,0 +1,131 @@
|
||||
#ifndef PLUGINSTRUCTS_P087_DATA_STRUCT_H
|
||||
#define PLUGINSTRUCTS_P087_DATA_STRUCT_H
|
||||
|
||||
#include <ESPeasySerial.h>
|
||||
#include "../../_Plugin_Helper.h"
|
||||
|
||||
#ifdef USES_P087
|
||||
# include <Regexp.h>
|
||||
|
||||
|
||||
# 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
|
||||
Reference in New Issue
Block a user