Skip to content

Commit 2d5181b

Browse files
authored
Merge pull request #5452 from tonhuisman/feature/Math-additional-functions-and-constants
[Rules] Add some functions, conversions and constants
2 parents 9a7ccb2 + f170eb7 commit 2d5181b

File tree

11 files changed

+212
-10
lines changed

11 files changed

+212
-10
lines changed

docs/source/Reference/SystemVariable.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,12 @@ The conversion always outputs a string, but not all of these can be converted ba
365365
* - mm to imperial: ``%c_mm2imp%(1900)``
366366
- mm to imperial: ``6'2.8"``
367367
- Millimeter to imperial units
368+
* - Degrees to radians: ``%c_d2r%(22)``
369+
- Degrees to radians: ``0.38``
370+
- Degrees to radians
371+
* - Radians to degrees: ``%c_r2d%(0.357)``
372+
- Radians to degrees: ``20.45``
373+
- Radians to degrees
368374
* - Mins to days: ``%c_m2day%(1900)``
369375
- Mins to days: ``1.32``
370376
- Minutes expressed in days

docs/source/Rules/Rules.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1587,9 +1587,11 @@ Basic Math Functions
15871587
* ``round(x)`` Rounds to the nearest integer, but rounds halfway cases away from zero (instead of to the nearest even integer).
15881588
* ``^`` The caret is used as the exponentiation operator for calculating the value of x to the power of y (x\ :sup:`y`).
15891589

1590-
* ``map(value:fromLow:fromHigh:toLow:toHigh)`` Maps value x in the fromLow/fromHigh range to toLow/toHigh values. Similar to the Arduino map() function. See examples below. (Using a colon as an argument separator to not interfere with regular argument processing)
1590+
* ``map(value:fromLow:fromHigh:toLow:toHigh)`` Maps ``value`` in the fromLow/fromHigh range to toLow/toHigh values. Similar to the Arduino map() function. See examples below. (Using a colon as an argument separator to not interfere with regular argument processing)
15911591
* ``mapc(value:fromLow:fromHigh:toLow:toHigh)`` same as map, but constrains the result to the fromLow/fromHigh range.
15921592

1593+
* ``fmod(x:y)`` Like the modulo operator ``%`` that returns an integer remainder, ``fmod`` returns the floating-point remainder of the division ``x / y``. Added: 2025-12-13 (Not available in limited builds)
1594+
15931595

15941596
Rules example:
15951597

@@ -1662,6 +1664,7 @@ Radian Angle:
16621664
* ``aSin(x)`` Arc Sine of x (radian)
16631665
* ``aCos(x)`` Arc Cosine of x (radian)
16641666
* ``aTan(x)`` Arc Tangent of x (radian)
1667+
* ``aTan2(x:y)`` Arc Tangent of x / y (radian) Added: 2025-12-13
16651668

16661669
Degree Angle:
16671670

@@ -1671,6 +1674,7 @@ Degree Angle:
16711674
* ``aSin_d(x)`` Arc Sine of x (degree)
16721675
* ``aCos_d(x)`` Arc Cosine of x (degree)
16731676
* ``aTan_d(x)`` Arc Tangent of x (degree)
1677+
* ``aTan2_d(x:y)`` Arc Tangent of x / y (degree) Added: 2025-12-13
16741678

16751679

16761680

src/src/Commands/Common.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,11 @@ String Command_GetORSetFloatMinMax(struct EventStruct *event,
244244
float _min,
245245
float _max)
246246
{
247-
bool hasArgument = false;
248247
{
249248
// Check if command is valid. Leave in separate scope to delete the TmpStr1
250249
String TmpStr1;
251250

252251
if (GetArgv(Line, TmpStr1, arg + 1)) {
253-
hasArgument = true;
254252
TmpStr1.toLowerCase();
255253

256254
float tmp_float{};

src/src/Helpers/Rules_calculate.cpp

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ bool RulesCalculate_t::is_unary_operator(char c)
7373
*/
7474
}
7575

76+
#ifndef LIMIT_BUILD_SIZE
77+
// binary = 2-argument functions
78+
bool RulesCalculate_t::is_binary_operator(char c)
79+
{
80+
return (c >= static_cast<char>(BinaryOperator::ArcTan2) &&
81+
c <= static_cast<char>(BinaryOperator::FMod));
82+
}
83+
#endif // ifndef LIMIT_BUILD_SIZE
84+
7685
// quinary = 5-argument functions
7786
bool RulesCalculate_t::is_quinary_operator(char c)
7887
{
@@ -271,6 +280,33 @@ ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_unary_operator(char op, ESPEASY
271280
return ret;
272281
}
273282

283+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
284+
ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_binary_operator(char op, ESPEASY_RULES_FLOAT_TYPE first, ESPEASY_RULES_FLOAT_TYPE second)
285+
{
286+
ESPEASY_RULES_FLOAT_TYPE ret{};
287+
const BinaryOperator bin_op = static_cast<BinaryOperator>(op);
288+
289+
const bool useDegree = angleDegree(bin_op);
290+
switch (bin_op) {
291+
case BinaryOperator::ArcTan2:
292+
case BinaryOperator::ArcTan2_d:
293+
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
294+
ret = atan2(first, second);
295+
#else
296+
ret = atan2f(first, second);
297+
#endif
298+
return useDegree ? degrees(ret) : ret;
299+
case BinaryOperator::FMod:
300+
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
301+
return fmod(first, second);
302+
#else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
303+
return fmodf(first, second);
304+
#endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
305+
}
306+
return ret;
307+
}
308+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
309+
274310
ESPEASY_RULES_FLOAT_TYPE RulesCalculate_t::apply_quinary_operator(char op,
275311
ESPEASY_RULES_FLOAT_TYPE first,
276312
ESPEASY_RULES_FLOAT_TYPE second,
@@ -328,6 +364,16 @@ CalculateReturnCode RulesCalculate_t::RPNCalculate(char *token)
328364

329365
// FIXME TD-er: Regardless whether it is an error, all code paths return ret;
330366
// if (isError(ret)) { return ret; }
367+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
368+
} else if (is_binary_operator(token[0]) && (token[1] == 0))
369+
{
370+
const ESPEASY_RULES_FLOAT_TYPE second = pop();
371+
const ESPEASY_RULES_FLOAT_TYPE first = pop();
372+
373+
ret = push(apply_binary_operator(token[0], first, second));
374+
// addLog(LOG_LEVEL_INFO, strformat(F("RPNCalculate binary %d: 1: %.4f 2: %.4f"), token[0], first, second));
375+
376+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
331377
} else if (is_quinary_operator(token[0]) && (token[1] == 0))
332378
{
333379
ESPEASY_RULES_FLOAT_TYPE fifth = pop();
@@ -464,7 +510,11 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL
464510
}
465511

466512
// If the token is any operator, op1, then:
467-
else if (is_operator(c) || is_unary_operator(c) || is_quinary_operator(c))
513+
else if (is_operator(c) || is_unary_operator(c) ||
514+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
515+
is_binary_operator(c) ||
516+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
517+
is_quinary_operator(c))
468518
{
469519
*(TokenPos) = 0; // Mark end of token string
470520
error = RPNCalculate(token);
@@ -568,7 +618,11 @@ CalculateReturnCode RulesCalculate_t::doCalculate(const char *input, ESPEASY_RUL
568618
sc = stack[sl - 2];
569619
if (is_operator(sc)) { // Not a function call
570620
// Don't touch
571-
} else if (is_unary_operator(sc) || is_quinary_operator(sc)) { // Function call, so process the function too
621+
} else if (is_unary_operator(sc) ||
622+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
623+
is_binary_operator(sc) ||
624+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
625+
is_quinary_operator(sc)) { // Function call, so process the function too
572626
*TokenPos = sc;
573627
++TokenPos;
574628
stack[sl - 2] = '\0'; // Don't process again on stack wind-down
@@ -666,6 +720,19 @@ void preProcessReplace(String& input, UnaryOperator op) {
666720
input.replace(find, replace);
667721
}
668722

723+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
724+
void preProcessReplace(String& input, BinaryOperator op) {
725+
String find = toString(op);
726+
727+
if (find.isEmpty()) { return; }
728+
find += '('; // Add opening parenthesis.
729+
730+
const String replace = String(static_cast<char>(op)) + '(';
731+
732+
input.replace(find, replace);
733+
}
734+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
735+
669736
bool angleDegree(UnaryOperator op)
670737
{
671738
switch (op) {
@@ -682,6 +749,22 @@ bool angleDegree(UnaryOperator op)
682749
return false;
683750
}
684751

752+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
753+
bool angleDegree(BinaryOperator op)
754+
{
755+
return BinaryOperator::ArcTan2_d == op;
756+
// switch (op) // Future extension for 4 or more BinaryOperator options and multiple _d options
757+
// {
758+
// case BinaryOperator::ArcTan2_d:
759+
// return true;
760+
// case BinaryOperator::ArcTan2:
761+
// case BinaryOperator::FMod:
762+
// return false;
763+
// }
764+
// return false;
765+
}
766+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
767+
685768
const __FlashStringHelper* toString(UnaryOperator op)
686769
{
687770
switch (op) {
@@ -733,6 +816,21 @@ const __FlashStringHelper* toString(UnaryOperator op)
733816
return F("");
734817
}
735818

819+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
820+
const __FlashStringHelper* toString(BinaryOperator op)
821+
{
822+
switch (op) {
823+
case BinaryOperator::ArcTan2:
824+
return F("atan2");
825+
case BinaryOperator::ArcTan2_d:
826+
return F("atan2_d");
827+
case BinaryOperator::FMod:
828+
return F("fmod");
829+
}
830+
return F("");
831+
}
832+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
833+
736834
String RulesCalculate_t::preProces(const String& input)
737835
{
738836
String preprocessed = input;
@@ -764,7 +862,7 @@ String RulesCalculate_t::preProces(const String& input)
764862
,UnaryOperator::Tan
765863
,UnaryOperator::Tan_d
766864
#endif // if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
767-
,UnaryOperator::Map
865+
,UnaryOperator::Map // FIXME ?Move to new QuintaryOperator enum?
768866
,UnaryOperator::MapC
769867

770868
};
@@ -783,6 +881,22 @@ String RulesCalculate_t::preProces(const String& input)
783881
preProcessReplace(preprocessed, op);
784882
}
785883
}
884+
885+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
886+
887+
const BinaryOperator operators2[] = {
888+
BinaryOperator::ArcTan2,
889+
BinaryOperator::ArcTan2_d,
890+
BinaryOperator::FMod,
891+
};
892+
893+
constexpr size_t nrOperators2 = NR_ELEMENTS(operators2);
894+
for (size_t i = 0; i < nrOperators2; ++i) {
895+
const BinaryOperator op = operators2[i];
896+
preProcessReplace(preprocessed, op);
897+
}
898+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
899+
786900
return preprocessed;
787901
}
788902

src/src/Helpers/Rules_calculate.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,23 @@ enum class UnaryOperator : uint8_t {
5858
MapC, // Map (value, lowFrom, highFrom, lowTo, highTo) and clamp to lowTo/highTo
5959
};
6060

61+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
62+
enum class BinaryOperator : uint8_t {
63+
64+
ArcTan2 = 220u, // Arc Tangent 2 (radian)
65+
ArcTan2_d, // Arc Tangent 2 (degrees)
66+
FMod, // Float-modulo
67+
};
68+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
69+
6170
void preProcessReplace(String & input,
6271
UnaryOperator op);
6372
bool angleDegree(UnaryOperator op);
6473
const __FlashStringHelper* toString(UnaryOperator op);
74+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
75+
bool angleDegree(BinaryOperator op);
76+
const __FlashStringHelper* toString(BinaryOperator op);
77+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
6578

6679
class RulesCalculate_t {
6780
private:
@@ -81,6 +94,10 @@ class RulesCalculate_t {
8194

8295
bool is_unary_operator(char c);
8396

97+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
98+
bool is_binary_operator(char c);
99+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
100+
84101
bool is_quinary_operator(char c);
85102

86103
CalculateReturnCode push(ESPEASY_RULES_FLOAT_TYPE value);
@@ -94,6 +111,12 @@ class RulesCalculate_t {
94111
ESPEASY_RULES_FLOAT_TYPE apply_unary_operator(char op,
95112
ESPEASY_RULES_FLOAT_TYPE first);
96113

114+
#if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
115+
ESPEASY_RULES_FLOAT_TYPE apply_binary_operator(char op,
116+
ESPEASY_RULES_FLOAT_TYPE first,
117+
ESPEASY_RULES_FLOAT_TYPE second);
118+
#endif // if !defined(LIMIT_BUILD_SIZE) && FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES
119+
97120
ESPEASY_RULES_FLOAT_TYPE apply_quinary_operator(char op,
98121
ESPEASY_RULES_FLOAT_TYPE first,
99122
ESPEASY_RULES_FLOAT_TYPE second,

src/src/Helpers/StringConverter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,10 @@ void parseStandardConversions(String& s, bool useURLencode) {
17091709
#if FEATURE_STRING_VARIABLES
17101710
SMART_CONV(F("%c_ts2wday%"), get_weekday_from_timestamp(static_cast<uint32_t>(data.arg1)))
17111711
#endif // if FEATURE_STRING_VARIABLES
1712+
#ifndef LIMIT_BUILD_SIZE
1713+
SMART_CONV(F("%c_d2r%"), doubleToString(radians(data.arg1)))
1714+
SMART_CONV(F("%c_r2d%"), doubleToString(degrees(data.arg1)))
1715+
#endif // ifndef LIMIT_BUILD_SIZE
17121716
#undef SMART_CONV
17131717

17141718
// Conversions with 2 parameters

src/src/Helpers/SystemVariables.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
#include "../Helpers/StringConverter.h"
3232
#include "../Helpers/StringProvider.h"
3333

34+
#ifndef LIMIT_BUILD_SIZE
35+
#include <math.h>
36+
#include "../Helpers/StringConverter_Numerical.h"
37+
#endif // ifndef LIMIT_BUILD_SIZE
3438

3539
#if defined(ESP8266)
3640
# include <ESP8266WiFi.h>
@@ -199,6 +203,24 @@ String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) {
199203
case LCLTIME_AM: return node_time.getDateTimeString_ampm('-', ':', ' ');
200204
case LF: return String('\n');
201205
case MAC_INT: intvalue = getChipId(); break; // Last 24 bit of MAC address as integer, to be used in rules.
206+
#ifndef LIMIT_BUILD_SIZE
207+
case S_PI: {
208+
constexpr ESPEASY_RULES_FLOAT_TYPE _pi = M_PI;
209+
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
210+
return doubleToString(_pi, maxNrDecimals_fpType(_pi));
211+
#else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
212+
return toString(_pi, maxNrDecimals_fpType(_pi));
213+
#endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
214+
}
215+
case S_E: {
216+
constexpr ESPEASY_RULES_FLOAT_TYPE _e = M_E;
217+
#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
218+
return doubleToString(_e, maxNrDecimals_fpType(_e));
219+
#else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
220+
return toString(_e, maxNrDecimals_fpType(_e));
221+
#endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE
222+
}
223+
#endif // ifndef LIMIT_BUILD_SIZE
202224
case SPACE: return String(' ');
203225
case SSID: return (WiFiEventData.WiFiDisconnected()) ? String(F("--")) : WiFi.SSID();
204226
case SYSBUILD_DATE: return get_build_date();
@@ -540,6 +562,10 @@ SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* begincha
540562
case 'd': return Enum::DNS;
541563
#if FEATURE_ETHERNET
542564
case 'e': return Enum::ETHCONNECTED;
565+
#else // if FEATURE_ETHERNET
566+
#ifndef LIMIT_BUILD_SIZE
567+
case 'e': return Enum::S_E;
568+
#endif // ifndef LIMIT_BUILD_SIZE
543569
#endif // if FEATURE_ETHERNET
544570
case 'f': return Enum::FLASH_CHIP_MODEL;
545571
case 'g': return Enum::GATEWAY;
@@ -551,6 +577,9 @@ SystemVariables::Enum SystemVariables::startIndex_beginWith(const char* begincha
551577
case 'l': return Enum::LCLTIME;
552578
case 'm': return Enum::SUNRISE_M;
553579
case 'n': return Enum::S_LF;
580+
#ifndef LIMIT_BUILD_SIZE
581+
case 'p': return Enum::S_PI;
582+
#endif // ifndef LIMIT_BUILD_SIZE
554583
case 'r': return Enum::S_CR;
555584
case 's': return Enum::SPACE;
556585
case 'u': return Enum::UNIT_sysvar;
@@ -578,6 +607,9 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum
578607
case Enum::DNS: return F("dns");
579608
case Enum::DNS_1: return F("dns1");
580609
case Enum::DNS_2: return F("dns2");
610+
#ifndef LIMIT_BUILD_SIZE
611+
case Enum::S_E: return F("e");
612+
#endif // ifndef LIMIT_BUILD_SIZE
581613
#if FEATURE_ETHERNET
582614
case Enum::ETHCONNECTED: return F("ethconnected");
583615
case Enum::ETHDUPLEX: return F("ethduplex");
@@ -621,6 +653,9 @@ const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum
621653
case Enum::MAC: return F("mac");
622654
case Enum::MAC_INT: return F("mac_int");
623655
case Enum::S_LF: return F("N");
656+
#ifndef LIMIT_BUILD_SIZE
657+
case Enum::S_PI: return F("pi");
658+
#endif // ifndef LIMIT_BUILD_SIZE
624659
case Enum::S_CR: return F("R");
625660
case Enum::RSSI: return F("rssi");
626661
case Enum::SPACE: return F("SP");

0 commit comments

Comments
 (0)