From 4b4b3709ad1bfc1a78af205c4f48ebd8d6f77003 Mon Sep 17 00:00:00 2001 From: Laurent <44267323+laurentdong@users.noreply.github.com> Date: Tue, 12 Feb 2019 21:46:42 -0500 Subject: [PATCH] Introduce Expression in Rules Support use an expression as paramter in some rule commands, include Var, Mem and Ruletimer. Expression is constructed by constants (float number), variables (var, mem, Time, Uptime, Sunrise, Sunset), operators and round brackets. Currently support 6 operators, order by priority from high to low: ^ (power) % (modulo) *, / +, - Commands examples: Var1 3.14 * (MEM1 * (10 + VAR2 ^2) - 100) % 10 + uptime / (2 + MEM2) Ruletimer4 Time - Sunrise + MEM2/2 --- lib/LinkedList-1.2.3/LICENSE.txt | 21 ++ lib/LinkedList-1.2.3/LinkedList.h | 325 +++++++++++++++++ lib/LinkedList-1.2.3/README.md | 171 +++++++++ .../examples/ClassList/ClassList.pde | 81 +++++ .../SimpleIntegerList/SimpleIntegerList.pde | 58 +++ lib/LinkedList-1.2.3/keywords.txt | 28 ++ lib/LinkedList-1.2.3/library.json | 12 + lib/LinkedList-1.2.3/library.properties | 9 + sonoff/my_user_config.h | 3 + sonoff/sonoff.ino | 4 +- sonoff/support.ino | 2 +- sonoff/xdrv_10_rules.ino | 330 ++++++++++++++++++ 12 files changed, 1042 insertions(+), 2 deletions(-) create mode 100644 lib/LinkedList-1.2.3/LICENSE.txt create mode 100644 lib/LinkedList-1.2.3/LinkedList.h create mode 100644 lib/LinkedList-1.2.3/README.md create mode 100644 lib/LinkedList-1.2.3/examples/ClassList/ClassList.pde create mode 100644 lib/LinkedList-1.2.3/examples/SimpleIntegerList/SimpleIntegerList.pde create mode 100644 lib/LinkedList-1.2.3/keywords.txt create mode 100644 lib/LinkedList-1.2.3/library.json create mode 100644 lib/LinkedList-1.2.3/library.properties 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]);