diff --git a/lib/LinkedList-1.2.3/LICENSE.txt b/lib/LinkedList-1.2.3/LICENSE.txt new file mode 100644 index 000000000..5c02604a0 --- /dev/null +++ b/lib/LinkedList-1.2.3/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Ivan Seidel + +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. diff --git a/lib/LinkedList-1.2.3/LinkedList.h b/lib/LinkedList-1.2.3/LinkedList.h new file mode 100644 index 000000000..371b14ac7 --- /dev/null +++ b/lib/LinkedList-1.2.3/LinkedList.h @@ -0,0 +1,325 @@ +/* + LinkedList.h - V1.1 - Generic LinkedList implementation + Works better with FIFO, because LIFO will need to + search the entire List to find the last one; + + For instructions, go to https://github.com/ivanseidel/LinkedList + + Created by Ivan Seidel Gomes, March, 2013. + Released into the public domain. +*/ + + +#ifndef LinkedList_h +#define LinkedList_h + +#include + +template +struct ListNode +{ + T data; + ListNode *next; +}; + +template +class LinkedList{ + +protected: + int _size; + ListNode *root; + ListNode *last; + + // Helps "get" method, by saving last position + ListNode *lastNodeGot; + int lastIndexGot; + // isCached should be set to FALSE + // everytime the list suffer changes + bool isCached; + + ListNode* getNode(int index); + +public: + LinkedList(); + ~LinkedList(); + + /* + Returns current size of LinkedList + */ + virtual int size(); + /* + Adds a T object in the specified index; + Unlink and link the LinkedList correcly; + Increment _size + */ + virtual bool add(int index, T); + /* + Adds a T object in the end of the LinkedList; + Increment _size; + */ + virtual bool add(T); + /* + Adds a T object in the start of the LinkedList; + Increment _size; + */ + virtual bool unshift(T); + /* + Set the object at index, with T; + Increment _size; + */ + virtual bool set(int index, T); + /* + Remove object at index; + If index is not reachable, returns false; + else, decrement _size + */ + virtual T remove(int index); + /* + Remove last object; + */ + virtual T pop(); + /* + Remove first object; + */ + virtual T shift(); + /* + Get the index'th element on the list; + Return Element if accessible, + else, return false; + */ + virtual T get(int index); + + /* + Clear the entire array + */ + virtual void clear(); + +}; + +// Initialize LinkedList with false values +template +LinkedList::LinkedList() +{ + root=NULL; + last=NULL; + _size=0; + + lastNodeGot = root; + lastIndexGot = 0; + isCached = false; +} + +// Clear Nodes and free Memory +template +LinkedList::~LinkedList() +{ + ListNode* tmp; + while(root!=NULL) + { + tmp=root; + root=root->next; + delete tmp; + } + last = NULL; + _size=0; + isCached = false; +} + +/* + Actualy "logic" coding +*/ + +template +ListNode* LinkedList::getNode(int index){ + + int _pos = 0; + ListNode* current = root; + + // Check if the node trying to get is + // immediatly AFTER the previous got one + if(isCached && lastIndexGot <= index){ + _pos = lastIndexGot; + current = lastNodeGot; + } + + while(_pos < index && current){ + current = current->next; + + _pos++; + } + + // Check if the object index got is the same as the required + if(_pos == index){ + isCached = true; + lastIndexGot = index; + lastNodeGot = current; + + return current; + } + + return false; +} + +template +int LinkedList::size(){ + return _size; +} + +template +bool LinkedList::add(int index, T _t){ + + if(index >= _size) + return add(_t); + + if(index == 0) + return unshift(_t); + + ListNode *tmp = new ListNode(), + *_prev = getNode(index-1); + tmp->data = _t; + tmp->next = _prev->next; + _prev->next = tmp; + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::add(T _t){ + + ListNode *tmp = new ListNode(); + tmp->data = _t; + tmp->next = NULL; + + if(root){ + // Already have elements inserted + last->next = tmp; + last = tmp; + }else{ + // First element being inserted + root = tmp; + last = tmp; + } + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::unshift(T _t){ + + if(_size == 0) + return add(_t); + + ListNode *tmp = new ListNode(); + tmp->next = root; + tmp->data = _t; + root = tmp; + + _size++; + isCached = false; + + return true; +} + +template +bool LinkedList::set(int index, T _t){ + // Check if index position is in bounds + if(index < 0 || index >= _size) + return false; + + getNode(index)->data = _t; + return true; +} + +template +T LinkedList::pop(){ + if(_size <= 0) + return T(); + + isCached = false; + + if(_size >= 2){ + ListNode *tmp = getNode(_size - 2); + T ret = tmp->next->data; + delete(tmp->next); + tmp->next = NULL; + last = tmp; + _size--; + return ret; + }else{ + // Only one element left on the list + T ret = root->data; + delete(root); + root = NULL; + last = NULL; + _size = 0; + return ret; + } +} + +template +T LinkedList::shift(){ + if(_size <= 0) + return T(); + + if(_size > 1){ + ListNode *_next = root->next; + T ret = root->data; + delete(root); + root = _next; + _size --; + isCached = false; + + return ret; + }else{ + // Only one left, then pop() + return pop(); + } + +} + +template +T LinkedList::remove(int index){ + if (index < 0 || index >= _size) + { + return T(); + } + + if(index == 0) + return shift(); + + if (index == _size-1) + { + return pop(); + } + + ListNode *tmp = getNode(index - 1); + ListNode *toDelete = tmp->next; + T ret = toDelete->data; + tmp->next = tmp->next->next; + delete(toDelete); + _size--; + isCached = false; + return ret; +} + + +template +T LinkedList::get(int index){ + ListNode *tmp = getNode(index); + + return (tmp ? tmp->data : T()); +} + +template +void LinkedList::clear(){ + while(size() > 0) + shift(); +} + +#endif diff --git a/lib/LinkedList-1.2.3/README.md b/lib/LinkedList-1.2.3/README.md new file mode 100644 index 000000000..bdb16fdbd --- /dev/null +++ b/lib/LinkedList-1.2.3/README.md @@ -0,0 +1,171 @@ +# LinkedList + +This library was developed targeting **`Arduino`** applications. However, works just great with any C++. + +Implementing a buffer for objects takes time. If we are not in the mood, we just create an `array[1000]` with enough size. + +The objective of this library is to create a pattern for projects. +If you need to use a List of: `int`, `float`, `objects`, `Lists` or `Wales`. **This is what you are looking for.** + +With a simple but powerful caching algorithm, you can get subsequent objects much faster than usual. Tested without any problems with Lists bigger than 2000 members. + +## Installation + +1. [Download](https://github.com/ivanseidel/LinkedList/archive/master.zip) the Latest release from gitHub. +2. Unzip and modify the Folder name to "LinkedList" (Remove the '-version') +3. Paste the modified folder on your Library folder (On your `Libraries` folder inside Sketchbooks or Arduino software). +4. Reopen the Arduino software. + +**If you are here, because another Library requires this class, just don't waste time reading bellow. Install and ready.** + +------------------------- + +## Getting started + +### The `LinkedList` class + +In case you don't know what a LinkedList is and what it's used for, take a quick look at [Wikipedia::LinkedList](https://en.wikipedia.org/wiki/Linked_list) before continuing. + +#### To declare a LinkedList object +```c++ +// Instantiate a LinkedList that will hold 'integer' +LinkedList myLinkedList = LinkedList(); + +// Or just this +LinkedList myLinkedList; + +// But if you are instantiating a pointer LinkedList... +LinkedList *myLinkedList = new LinkedList(); + +// If you want a LinkedList with any other type such as 'MyClass' +// Make sure you call delete(MyClass) when you remove! +LinkedList *myLinkedList = new LinkedList(); +``` + +#### Getting the size of the linked list +```c++ +// To get the size of a linked list, make use of the size() method +int theSize = myList.size(); + +// Notice that if it's pointer to the linked list, you should use -> instead +int theSize = myList->size(); +``` + +#### Adding elements + +```c++ +// add(obj) method will insert at the END of the list +myList.add(myObject); + +// add(index, obj) method will try to insert the object at the specified index +myList.add(0, myObject); // Add at the beginning +myList.add(3, myObject); // Add at index 3 + +// unshift(obj) method will insert the object at the beginning +myList.unshift(myObject); +``` + +#### Getting elements + +```c++ +// get(index) will return the element at index +// (notice that the start element is 0, not 1) + +// Get the FIRST element +myObject = myList.get(0); + +// Get the third element +myObject = myList.get(2); + +// Get the LAST element +myObject = myList.get(myList.size() - 1); +``` + +#### Changing elements +```c++ +// set(index, obj) method will change the object at index to obj + +// Change the first element to myObject +myList.set(0, myObject); + +// Change the third element to myObject +myList.set(2, myObject); + +// Change the LAST element of the list +myList.set(myList.size() - 1, myObject); +``` + +#### Removing elements +```c++ +// remove(index) will remove and return the element at index + +// Remove the first object +myList.remove(0); + +// Get and Delete the third element +myDeletedObject = myList.remove(2); + +// pop() will remove and return the LAST element +myDeletedObject = myList.pop(); + +// shift() will remove and return the FIRST element +myDeletedObject = myList.shift(); + +// clear() will erase the entire list, leaving it with 0 elements +// NOTE: Clear wont DELETE/FREE memory from Pointers, if you +// are using Classes/Poiners, manualy delete and free those. +myList.clear(); +``` + +------------------------ + +## Library Reference + +### `ListNode` struct + +- `T` `ListNode::data` - The object data + +- `ListNode` `*next` - Pointer to the next Node + +### `LinkedList` class + +**`boolean` methods returns if succeeded** + +- `LinkedList::LinkedList()` - Constructor. + +- `LinkedList::~LinkedList()` - Destructor. Clear Nodes to minimize memory. Does not free pointer memory. + +- `int` `LinkedList::size()` - Returns the current size of the list. + +- `bool` `LinkedList::add(T)` - Add element T at the END of the list. + +- `bool` `LinkedList::add(int index, T)` - Add element T at `index` of the list. + +- `bool` `LinkedList::unshift(T)` - Add element T at the BEGINNING of the list. + +- `bool` `LinkedList::set(int index, T)` - Set the element at `index` to T. + +- `T` `LinkedList::remove(int index)` - Remove element at `index`. Return the removed element. Does not free pointer memory + +- `T` `LinkedList::pop()` - Remove the LAST element. Return the removed element. + +- `T` `LinkedList::shift()` - Remove the FIRST element. Return the removed element. + +- `T` `LinkedList::get(int index)` - Return the element at `index`. + +- `void` `LinkedList::clear()` - Removes all elements. Does not free pointer memory. + +- **protected** `int` `LinkedList::_size` - Holds the cached size of the list. + +- **protected** `ListNode` `LinkedList::*root` - Holds the root node of the list. + +- **protected** `ListNode` `LinkedList::*last` - Holds the last node of the list. + +- **protected** `ListNode*` `LinkedList::getNode(int index)` - Returns the `index` node of the list. + +### Version History + +* `1.1 (2013-07-20)`: Cache implemented. Getting subsequent objects is now O(N). Before, O(N^2). +* `1.0 (2013-07-20)`: Original release + +![LinkedList](https://d2weczhvl823v0.cloudfront.net/ivanseidel/LinkedList/trend.png) diff --git a/lib/LinkedList-1.2.3/examples/ClassList/ClassList.pde b/lib/LinkedList-1.2.3/examples/ClassList/ClassList.pde new file mode 100644 index 000000000..9a8ea9d99 --- /dev/null +++ b/lib/LinkedList-1.2.3/examples/ClassList/ClassList.pde @@ -0,0 +1,81 @@ +/* + LinkedList Example + Link: http://github.com/ivanseidel/LinkedList + + Example Created by + Tom Stewart, github.com/tastewar + + Edited by: + Ivan Seidel, github.com/ivanseidel +*/ + +#include + +// Let's define a new class +class Animal { + public: + char *name; + bool isMammal; +}; + +char catname[]="kitty"; +char dogname[]="doggie"; +char emuname[]="emu"; + +LinkedList myAnimalList = LinkedList(); + +void setup() +{ + + Serial.begin(9600); + Serial.println("Hello!" ); + + // Create a Cat + Animal *cat = new Animal(); + cat->name = catname; + cat->isMammal = true; + + // Create a dog + Animal *dog = new Animal(); + dog->name = dogname; + dog->isMammal = true; + + // Create a emu + Animal *emu = new Animal(); + emu->name = emuname; + emu->isMammal = false; // just an example; no offense to pig lovers + + // Add animals to list + myAnimalList.add(cat); + myAnimalList.add(emu); + myAnimalList.add(dog); +} + +void loop() { + + Serial.print("There are "); + Serial.print(myAnimalList.size()); + Serial.print(" animals in the list. The mammals are: "); + + int current = 0; + Animal *animal; + for(int i = 0; i < myAnimalList.size(); i++){ + + // Get animal from list + animal = myAnimalList.get(i); + + // If its a mammal, then print it's name + if(animal->isMammal){ + + // Avoid printing spacer on the first element + if(current++) + Serial.print(", "); + + // Print animal name + Serial.print(animal->name); + } + } + Serial.println("."); + + while (true); // nothing else to do, loop forever +} \ No newline at end of file diff --git a/lib/LinkedList-1.2.3/examples/SimpleIntegerList/SimpleIntegerList.pde b/lib/LinkedList-1.2.3/examples/SimpleIntegerList/SimpleIntegerList.pde new file mode 100644 index 000000000..1bcbe9c37 --- /dev/null +++ b/lib/LinkedList-1.2.3/examples/SimpleIntegerList/SimpleIntegerList.pde @@ -0,0 +1,58 @@ +/* + LinkedList Example + Link: http://github.com/ivanseidel/LinkedList + + Example Created by + Tom Stewart, github.com/tastewar + + Edited by: + Ivan Seidel, github.com/ivanseidel +*/ +#include + +LinkedList myList = LinkedList(); + +void setup() +{ + + Serial.begin(9600); + Serial.println("Hello!"); + + // Add some stuff to the list + int k = -240, + l = 123, + m = -2, + n = 222; + myList.add(n); + myList.add(0); + myList.add(l); + myList.add(17); + myList.add(k); + myList.add(m); +} + +void loop() { + + int listSize = myList.size(); + + Serial.print("There are "); + Serial.print(listSize); + Serial.print(" integers in the list. The negative ones are: "); + + // Print Negative numbers + for (int h = 0; h < listSize; h++) { + + // Get value from list + int val = myList.get(h); + + // If the value is negative, print it + if (val < 0) { + Serial.print(" "); + Serial.print(val); + } + } + + while (true); // nothing else to do, loop forever +} + + diff --git a/lib/LinkedList-1.2.3/keywords.txt b/lib/LinkedList-1.2.3/keywords.txt new file mode 100644 index 000000000..3ae496859 --- /dev/null +++ b/lib/LinkedList-1.2.3/keywords.txt @@ -0,0 +1,28 @@ +####################################### +# Syntax Coloring +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +LinkedList KEYWORD1 +ListNode KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +size KEYWORD2 +add KEYWORD2 +unshift KEYWORD2 +set KEYWORD2 +remove KEYWORD2 +pop KEYWORD2 +shift KEYWORD2 +get KEYWORD2 +clear KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/lib/LinkedList-1.2.3/library.json b/lib/LinkedList-1.2.3/library.json new file mode 100644 index 000000000..4179b248d --- /dev/null +++ b/lib/LinkedList-1.2.3/library.json @@ -0,0 +1,12 @@ +{ + "name": "LinkedList", + "keywords": "pattern", + "description": "A fully implemented LinkedList (int, float, objects, Lists or Wales) made to work with Arduino projects", + "repository": + { + "type": "git", + "url": "https://github.com/ivanseidel/LinkedList.git" + }, + "frameworks": "arduino", + "platforms": "*" +} diff --git a/lib/LinkedList-1.2.3/library.properties b/lib/LinkedList-1.2.3/library.properties new file mode 100644 index 000000000..77b1423c0 --- /dev/null +++ b/lib/LinkedList-1.2.3/library.properties @@ -0,0 +1,9 @@ +name=LinkedList +version=1.2.3 +author=Ivan Seidel +maintainer=Ivan Seidel +sentence=A fully implemented LinkedList made to work with Arduino projects +paragraph=The objective of this library is to create a pattern for projects. If you need to use a List of: int, float, objects, Lists or Wales. This is what you are looking for. +category=Data Processing +url=https://github.com/ivanseidel/LinkedList +architectures=* diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h index 3029a3dc4..aa666cfd5 100644 --- a/sonoff/my_user_config.h +++ b/sonoff/my_user_config.h @@ -278,6 +278,9 @@ // -- Rules --------------------------------------- #define USE_RULES // Add support for rules (+4k4 code) +#ifdef USE_RULES + #define USE_EXPRESSION // Add support for expression evaluation in rules (+3k1 code, +28 bytes mem) +#endif // -- Internal Analog input ----------------------- #define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index 34c422d20..b03f1a335 100755 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -64,7 +64,9 @@ #ifdef USE_SPI #include // SPI support, TFT #endif // USE_SPI - +#ifdef USE_EXPRESSION + #include // Import LinkedList library +#endif // Structs #include "settings.h" diff --git a/sonoff/support.ino b/sonoff/support.ino index c728d28eb..9cc18b8b4 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -154,7 +154,7 @@ char* subStr(char* dest, char* str, const char *delim, int index) return sub; } -double CharToDouble(char *str) +double CharToDouble(const char *str) { // simple ascii to double, because atof or strtod are too large char strbuf[24]; diff --git a/sonoff/xdrv_10_rules.ino b/sonoff/xdrv_10_rules.ino index 327c128dc..18679ada0 100644 --- a/sonoff/xdrv_10_rules.ino +++ b/sonoff/xdrv_10_rules.ino @@ -90,6 +90,19 @@ #define MAXIMUM_COMPARE_OPERATOR COMPARE_OPERATOR_SMALLER_EQUAL const char kCompareOperators[] PROGMEM = "=\0>\0<\0|\0==!=>=<="; +#ifdef USE_EXPRESSION + const char kExpressionOperators[] PROGMEM = "+-*/%^"; + #define EXPRESSION_OPERATOR_ADD 0 + #define EXPRESSION_OPERATOR_SUBTRACT 1 + #define EXPRESSION_OPERATOR_MULTIPLY 2 + #define EXPRESSION_OPERATOR_DIVIDEDBY 3 + #define EXPRESSION_OPERATOR_MODULO 4 + #define EXPRESSION_OPERATOR_POWER 5 + + const uint8_t kExpressionOperatorsPriorities[] PROGMEM = {1, 1, 2, 2, 3, 4}; + #define MAX_EXPRESSION_OPERATOR_PRIORITY 4 +#endif //USE_EXPRESSION + enum RulesCommands { CMND_RULE, CMND_RULETIMER, CMND_EVENT, CMND_VAR, CMND_MEM, CMND_ADD, CMND_SUB, CMND_MULT, CMND_SCALE, CMND_CALC_RESOLUTION }; const char kRulesCommands[] PROGMEM = D_CMND_RULE "|" D_CMND_RULETIMER "|" D_CMND_EVENT "|" D_CMND_VAR "|" D_CMND_MEM "|" D_CMND_ADD "|" D_CMND_SUB "|" D_CMND_MULT "|" D_CMND_SCALE "|" D_CMND_CALC_RESOLUTION ; @@ -562,6 +575,310 @@ void RulesTeleperiod(void) rules_teleperiod = 0; } +#ifdef USE_EXPRESSION +/********************************************************************************************/ +/* + * Parse a number value + * Input: + * pNumber - A char pointer point to a digit started string (guaranteed) + * value - Reference a double variable used to accept the result + * Output: + * pNumber - Pointer forward to next character after the number + * value - double type, the result value + * Return: + * true - succeed + * false - failed + */ +bool findNextNumber(char * &pNumber, double &value) +{ + bool bSucceed = false; + String sNumber = ""; + while (*pNumber) { + if (isdigit(*pNumber) || (*pNumber == '.')) { + sNumber += *pNumber; + pNumber++; + } else { + break; + } + } + if (sNumber.length() > 0) { + value = CharToDouble(sNumber.c_str()); + bSucceed = true; + } + return bSucceed; +} + +/********************************************************************************************/ +/* + * Parse a variable (like VAR1, MEM3) and get its value (double type) + * Input: + * pVarname - A char pointer point to a variable name string + * value - Reference a double variable used to accept the result + * Output: + * pVarname - Pointer forward to next character after the variable + * value - double type, the result value + * Return: + * true - succeed + * false - failed + */ +bool findNextVariableValue(char * &pVarname, double &value) +{ + bool succeed = false; + value = 0; + String sVarName = ""; + while (*pVarname) { + if (isalpha(*pVarname) || isdigit(*pVarname)) { + sVarName.concat(*pVarname); + pVarname++; + } else { + break; + } + } + sVarName.toUpperCase(); + if (sVarName.startsWith("VAR")) { + int index = sVarName.substring(3).toInt(); + if (index > 0 && index <= MAX_RULE_VARS) { + value = CharToDouble(vars[index -1]); + succeed = true; + } + } else if (sVarName.startsWith("MEM")) { + int index = sVarName.substring(3).toInt(); + if (index > 0 && index <= MAX_RULE_MEMS) { + value = CharToDouble(Settings.mems[index -1]); + succeed = true; + } + } else if (sVarName.equals("TIME")) { + value = GetMinutesPastMidnight(); + succeed = true; + } else if (sVarName.equals("UPTIME")) { + value = GetMinutesUptime(); + succeed = true; +#if defined(USE_TIMERS) && defined(USE_SUNRISE) + } else if (sVarName.equals("SUNRISE")) { + value = GetSunMinutes(0); + succeed = true; + } else if (sVarName.equals("SUNSET")) { + value = GetSunMinutes(1); + succeed = true; +#endif + } + + return succeed; +} + +/********************************************************************************************/ +/* + * Find next object in expression and evaluate it + * An object could be: + * - A float number start with a digit, like 0.787 + * - A variable name, like VAR1, MEM3 + * - An expression enclosed with a pair of round brackets, (.....) + * Input: + * pointer - A char pointer point to a place of the expression string + * value - Reference a double variable used to accept the result + * Output: + * pointer - Pointer forward to next character after next object + * value - double type, the result value + * Return: + * true - succeed + * false - failed + */ +bool findNextObjectValue(char * &pointer, double &value) +{ + bool bSucceed = false; + while (*pointer) + { + if (isspace(*pointer)) { //Skip leading spaces + pointer++; + continue; + } + if (isdigit(*pointer)) { //This object is a number + bSucceed = findNextNumber(pointer, value); + break; + } else if (isalpha(*pointer)) { //Should be a variable like VAR12, MEM1 + bSucceed = findNextVariableValue(pointer, value); + break; + } else if (*pointer == '(') { //It is a sub expression bracketed with () + pointer++; + char * sub_exp_start = pointer; //Find out the sub expression between a pair of parenthesis. "()" + unsigned int sub_exp_len = 0; + //Look for the matched closure parenthesis.")" + bool bFindClosures = false; + uint8_t matchClosures = 1; + while (*pointer) + { + if (*pointer == ')') { + matchClosures--; + if (matchClosures == 0) { + sub_exp_len = pointer - sub_exp_start; + bFindClosures = true; + break; + } + } else if (*pointer == '(') { + matchClosures++; + } + pointer++; + } + if (bFindClosures) { + value = evaluateExpression(sub_exp_start, sub_exp_len); + bSucceed = true; + } + break; + } else { //No number, no variable, no expression, then invalid object. + break; + } + } + return bSucceed; +} + +/********************************************************************************************/ +/* + * Find next operator in expression + * An operator could be: +, - , * , / , %, ^ + * Input: + * pointer - A char pointer point to a place of the expression string + * op - Reference to a variable used to accept the result + * Output: + * pointer - Pointer forward to next character after next operator + * op - The operator. 0, 1, 2, 3, 4, 5 + * Return: + * true - succeed + * false - failed + */ +bool findNextOperator(char * &pointer, int8_t &op) +{ + bool bSucceed = false; + while (*pointer) + { + if (isspace(*pointer)) { //Skip leading spaces + pointer++; + continue; + } + if (char *pch = strchr(kExpressionOperators, *pointer)) { //If it is an operator + op = (int8_t)(pch - kExpressionOperators); + pointer++; + bSucceed = true; + } + break; + } + return bSucceed; +} +/********************************************************************************************/ +/* + * Calculate a simple expression composed by 2 value and 1 operator, like 2 * 3 + * Input: + * pointer - A char pointer point to a place of the expression string + * value - Reference a double variable used to accept the result + * Output: + * pointer - Pointer forward to next character after next object + * value - double type, the result value + * Return: + * true - succeed + * false - failed + */ +double calculateTwoValues(double v1, double v2, uint8_t op) +{ + switch (op) + { + case EXPRESSION_OPERATOR_ADD: + return v1 + v2; + case EXPRESSION_OPERATOR_SUBTRACT: + return v1 - v2; + case EXPRESSION_OPERATOR_MULTIPLY: + return v1 * v2; + case EXPRESSION_OPERATOR_DIVIDEDBY: + return (0 == v2) ? 0 : (v1 / v2); + case EXPRESSION_OPERATOR_MODULO: + return (0 == v2) ? 0 : (int(v1) % int(v2)); + case EXPRESSION_OPERATOR_POWER: + return FastPrecisePow(v1, v2); + } + return 0; +} + +/********************************************************************************************/ +/* + * Parse and evaluate an expression. + * For example: "10 * ( MEM2 + 1) / 2" + * Right now, only support operators listed here: (order by priority) + * Priority 4: ^ (power) + * Priority 3: % (modulo, always get integer result) + * Priority 2: *, / + * Priority 1: +, - + * Input: + * expression - The expression to be evaluated + * len - Length of the expression + * Return: + * double - result. + * 0 - if the expression is invalid + * An example: + * MEM1 = 3, MEM2 = 6, VAR2 = 15, VAR10 = 80 + * At beginning, the expression might be complicated like: 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + VAR10 / (2 + MEM2) + * We are going to scan the whole expression, evaluate each object. + * Finally we will have a value list:. + * Order Object Value + * 0 3.14 3.14 + * 1 (MEM1 * (10 + VAR2 ^2) - 100) 605 + * 2 10 10 + * 3 VAR10 80 + * 4 (2 + MEM2) 8 + * And an operator list: + * Order Operator Priority + * 0 * 2 + * 1 % 3 + * 2 + 1 + * 3 / 2 + */ +double evaluateExpression(const char * expression, unsigned int len) +{ + char expbuf[len + 1]; + memcpy(expbuf, expression, len); + expbuf[len] = '\0'; + char * scan_pointer = expbuf; + + LinkedList object_values; + LinkedList operators; + int8_t op; + double va; + //Find and add the value of first object + if (findNextObjectValue(scan_pointer, va)) { + object_values.add(va); + } else { + return 0; + } + while (*scan_pointer) + { + if (findNextOperator(scan_pointer, op) + && *scan_pointer + && findNextObjectValue(scan_pointer, va)) + { + operators.add(op); + object_values.add(va); + } else { + //No operator followed or no more object after this operator, we done. + break; + } + } + + //Going to evaluate the whole expression + //Calculate by order of operator priorities. Looking for all operators with specified priority (from High to Low) + for (int8_t priority = MAX_EXPRESSION_OPERATOR_PRIORITY; priority>0; priority--) { + int index = 0; + while (index < operators.size()) { + if (priority == kExpressionOperatorsPriorities[(operators.get(index))]) { //need to calculate the operator first + //get current object value and remove the next object with current operator + va = calculateTwoValues(object_values.get(index), object_values.remove(index + 1), operators.remove(index)); + //Replace the current value with the result + object_values.set(index, va); + } else { + index++; + } + } + } + return object_values.get(0); +} +#endif //USE_EXPRESSION + bool RulesCommand(void) { char command[CMDSZ]; @@ -620,7 +937,12 @@ bool RulesCommand(void) } else if ((CMND_RULETIMER == command_code) && (index > 0) && (index <= MAX_RULE_TIMERS)) { if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + double timer_set = evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len); + rules_timer[index -1] = (timer_set > 0) ? millis() + (1000 * timer_set) : 0; +#else rules_timer[index -1] = (XdrvMailbox.payload > 0) ? millis() + (1000 * XdrvMailbox.payload) : 0; +#endif //USE_EXPRESSION } mqtt_data[0] = '\0'; for (uint8_t i = 0; i < MAX_RULE_TIMERS; i++) { @@ -636,14 +958,22 @@ bool RulesCommand(void) } else if ((CMND_VAR == command_code) && (index > 0) && (index <= MAX_RULE_VARS)) { if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + dtostrfd(evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len), Settings.flag2.calc_resolution, vars[index -1]); +#else strlcpy(vars[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(vars[index -1])); +#endif //USE_EXPRESSION bitSet(vars_event, index -1); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, vars[index -1]); } else if ((CMND_MEM == command_code) && (index > 0) && (index <= MAX_RULE_MEMS)) { if (XdrvMailbox.data_len > 0) { +#ifdef USE_EXPRESSION + dtostrfd(evaluateExpression(XdrvMailbox.data, XdrvMailbox.data_len), Settings.flag2.calc_resolution, Settings.mems[index -1]); +#else strlcpy(Settings.mems[index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[index -1])); +#endif //USE_EXPRESSION bitSet(mems_event, index -1); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mems[index -1]);