diff --git a/platformio.ini b/platformio.ini
index f721a496a..1d80293cb 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -22,7 +22,7 @@ src_dir = sonoff
;env_default = sonoff-BR
;env_default = sonoff-CN
;env_default = sonoff-CZ
-;env_default = sonoff-DE
+env_default = sonoff-DE
;env_default = sonoff-ES
;env_default = sonoff-FR
;env_default = sonoff-GR
@@ -70,14 +70,14 @@ build_flags = ${esp82xx_defaults.build_flags}
platform = espressif8266@~2.1.1
build_flags = ${esp82xx_defaults.build_flags}
-Wl,-Teagle.flash.1m.ld
-; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
+; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
-O2
-DBEARSSL_SSL_BASIC
; nonos-sdk 22x
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x
; nonos-sdk-pre-v3
; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
-; lwIP 1.4
+; lwIP 1.4
; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; lwIP 2 - Low Memory
; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
@@ -96,13 +96,13 @@ build_flags = ${esp82xx_defaults.build_flags}
platform = https://github.com/platformio/platform-espressif8266.git#feature/stage
build_flags = ${esp82xx_defaults.build_flags}
-Wl,-Teagle.flash.1m.ld
-; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
+; Code optimization see https://github.com/esp8266/Arduino/issues/5790#issuecomment-475672473
-O2
-DBEARSSL_SSL_BASIC
; nonos-sdk 22x
-DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x
; nonos-sdk-pre-v3
-; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
+; -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3
; lwIP 1.4
; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH
; lwIP 2 - Low Memory
diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h
index 945261e1c..a7d7a3602 100644
--- a/sonoff/my_user_config.h
+++ b/sonoff/my_user_config.h
@@ -290,6 +290,9 @@
// -- Rules ---------------------------------------
#define USE_RULES // Add support for rules (+4k4 code)
+#undef USE_RULES
+#define USE_SCRIPT
+
// #define USE_EXPRESSION // Add support for expression evaluation in rules (+3k2 code, +64 bytes mem)
// #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code)
diff --git a/sonoff/scripter.md b/sonoff/scripter.md
new file mode 100644
index 000000000..5b9ac7840
--- /dev/null
+++ b/sonoff/scripter.md
@@ -0,0 +1,621 @@
+**Script Language for Tasmota**
+
+As an alternative to rules. (about 14,2k flash size, variable ram size)
+
+In submenu Configuration =\> edit script
+1535 bytes max script size (uses rules buffer)
+
+to enable:
+\#define USE_SCRIPT
+\#undef USE_RULES
+
+
+Up to 50 variables (45 numeric and 5 strings, maybe changed by #define)
+Freely definable variable names (all names are intentionally case sensitive)
+Nested if,then,else up to a level of 8
+Math operators **+,-,\*,/,%,&,|,^**
+all operators may be used in the op= form e.g. **+=**
+Left right evaluation with optional brackets
+all numbers are float
+e.g. temp=hum\*(100/37.5)+temp-(timer\*hum%10)
+no spaces allowed between math operations
+Comparison operators **==,!=,\>,\>=,<,<=**
+**and** , **or** support
+
+strings support **+** and **+=** operators
+string comparison **==,!=**
+max string size = 19 chars (default, can be increased or decreased by optional >D parameter)
+
+**Comments** start with **;**
+
+**Sections** defined:
+
+>**\>D ssize**
+ssize = optional max stringsize (default=19)
+define and init variables here, must be the first section, no other code allowed
+**p:**vname specifies permanent vars (the number of permanent vars is limited by tasmota rules space (50 bytes)
+numeric var=4 bytes, string var=lenght of string+1)
+**t:**vname specifies countdown timers, if >0 they are decremented in seconds until zero is reached. see example below
+**i:**vname specifies auto increment counters if >=0 (in seconds)
+**m:**vname specifies a median filter variable with 5 entries (for elimination of outliers)
+**M:**vname specifies a moving average filter variable with 8 entries (for smoothing data)
+(max 5 filters in total m+M)
+
+>all variable names length taken together may not exceed 256 characters, so keep variable names as short as possible.
+memory is dynamically allocated as a result of the D section.
+copying a string to a number or reverse is supported
+
+>**\>B**
+executed on BOOT time
+
+>**\>T**
+executed on teleperiod time (**SENSOR** and **STATE**), get tele vars only in this section
+
+>**\>S**
+executed every second
+
+>**\>E**
+executed e.g. on power change and mqtt **RESULT**
+
+>**\>R**
+executed on restart, p vars are saved automatically after this call
+
+
+special variables (read only):
+
+>**upsecs** = seconds since start
+**uptime** = minutes since start
+**time** = minutes since midnight
+**sunrise** = sunrise minutes since midnight
+**sunset** = sunset minutes since midnight
+**tper** = teleperiod (may be set also)
+**tstamp** = timestamp (local date and time)
+**topic** = mqtt topic
+**gtopic** = mqtt group topic
+**prefixn** = prefix n = 1-3
+**pwr[x]** = tasmota power state (x = 1-N)
+**sw[x]** = tasmota switch state (x = 1-N)
+>**pin[x]** = gpio pin level (x = 0-16)
+**pn[x]** = pin number for sensor code x, 99 if none
+**pd[x]** = defined sensor for gpio pin nr x none=999
+**gtmp** = global temperature
+**ghum** = global humidity
+**gprs** = global pressure
+**pow(x y)** = calculates the power of x^y
+**med(n x)** = calculates a 5 value median filter of x (2 filters possible n=0,1)
+**int(x)** = gets the integer part of x (like floor)
+**hn(x)** = converts x (0..255) zu a hex nibble string
+**mqtts** = state of mqtt disconnected=0, connected>0
+**wifis** = state of wifi disconnected=0, connected>0
+
+>**hours** = hours
+**mins** = mins
+**secs** = seconds
+**day** = day of month
+**wday** = day of week
+**month** = month
+**year** = year
+
+these variables are cleared after reading true
+>**chg[var]** = true if a variables value was changed (numeric vars only)
+**upd[var]** = true if a variable was updated
+**boot** = true on BOOT
+**tinit** = true on time init
+**tset** = true on time set
+**mqttc** = true on mqtt connect
+**mqttd** = true on mqtt disconnect
+**wific** = true on wifi connect
+**wifid** = true on wifi disconnect
+
+system vars (for debugging)
+>**stack** = stack size
+**heap** = heap size
+**ram** = used ram size
+**slen** = script length
+**micros** = running microseconds
+**millis** = running milliseconds
+**loglvl** = loglevel of script cmds, may be set also
+
+remarks:
+if you define a variable with the same name as a special
+variable that special variable is discarded
+
+
+**Tasmota** cmds start with **=\>**
+within cmds you can replace text with variables with **%varname%**
+a single percent sign must be given as **%%**
+
+**special** cmds:
+
+>**=\> print** prints to info log for debugging
+
+to save code space nearly no error messages are provided. However it is taken care of that at least it should not crash on syntax errors.
+if a variable does not exist a **???** is given on commands
+if a **SENSOR** or **STATUS** or **RESULT** message or a var does not exist the destination variable is NOT updated.
+
+2 possibilities for conditionals:
+>**if** a==b
+**and** x==y
+**or** k==i
+**then** => do this
+**else** => do that
+**endif**
+
+OR
+
+>**if** a==b
+**and** x==y
+**or** k==i **{**
+ => do this
+**} else {**
+ => do that
+**}**
+
+you may NOT mix both methods
+
+also possible e.g.
+
+>if var1-var2==var3*var4
+then
+
+remarks:
+the last closing bracket must be on a single line
+the condition may not be enclosed in brackets
+
+>**break** exits a section or terminates a for next loop
+**dprecx** sets decimal precision to x (0-9)
+**svars** save permanent vars
+**delay(x)** pauses x milliseconds (should be as short as possible)
+**spin(x m)** set gpio pin x (0-16) to value m (0,1) only the last bit is used, so even values set the pin to zero and uneven values set the pin to 1
+**spinm(x m)** set pin mode gpio pin x (0-16) to mode m (input=0,output=1)
+
+>**#name** names a subroutine, subroutines are called with **=#name**
+**#name(param)** names a subroutines with a parameter is called with **=#name(param)**
+subroutines end with the next '#' or '>' line or break, may be nested
+params can be numbers or strings and on mismatch are converted
+
+>**for var from to inc**
+**next**
+specifies a for next loop, (loop count must not be less then 1)
+
+>**switch x**
+**case a**
+**case b**
+**ends**
+specifies a switch case selector
+
+**konsole script cmds**
+>**script 1 or 0** switch script on or off
+**script >cmdline** executes the script cmdline
+can be used e.g. to set variables e.g. **script >mintmp=15**
+more then one line may be executed seperated by a semicolon e.g. **script >mintmp=15;maxtemp=40**
+script itself cant be set because the size would not fit the mqtt buffers
+
+***example script***
+meant to show some of the possibilities
+(actually this code ist too large)
+
+**\>D**
+; define all vars here
+p:mintmp=10 (p:means permanent)
+p:maxtmp=30
+t:timer1=30 (t:means countdown timer)
+t:mt=0
+i:count=0 (i:means auto counter)
+hello="hello world"
+string="xxx"
+url="[192.168.178.86]"
+hum=0
+temp=0
+timer=0
+dimmer=0
+sw=0
+rssi=0
+param=0
+
+col=""
+ocol=""
+chan1=0
+chan2=0
+chan3=0
+
+ahum=0
+atemp=0
+tcnt=0
+hour=0
+state=1
+m:med5=0
+M:movav=0
+
+**\>B**
+
+string=hello+"how are you?"
+=\>print BOOT executed
+=\>print %hello%
+=\>mp3track 1
+
+; list gpio pin definitions
+for cnt 0 16 1
+tmp=pd[cnt]
+=>print %cnt% = %tmp%
+next
+
+; get gpio pin for relais 1
+tmp=pn[21]
+=>print relais 1 is on pin %tmp%
+
+; pulse relais over raw gpio
+spin(tmp 1)
+delay(100)
+spin(tmp 0)
+
+; raw pin level
+=>print level of gpio1 %pin[1]%
+
+; pulse over tasmota cmd
+=>power 1
+delay(100)
+=>power 0
+
+**\>T**
+
+hum=BME280#Humidity
+temp=BME280#Temperature
+rssi=Wifi#RSSI
+string=SleepMode
+
+; add to median filter
+median=temp
+; add to moving average filter
+movav=hum
+
+; show filtered results
+=>print %median% %movav%
+
+if chg[rssi]>0
+then =>print rssi changed to %rssi%
+endif
+
+if temp\>30
+and hum\>70
+then =\>print damn hot!
+endif
+
+**\>S**
+
+; every second but not completely reliable time here
+; use upsecs and uptime or best t: for reliable timers
+
+; call subrountines with parameters
+=#sub1("hallo")
+=#sub2(999)
+
+; stop timer after expired
+if timer1==0
+then timer1=-1
+=>print timer1 expired
+endif
+
+; auto counter with restart
+if count>=10
+then =>print 10 seconds over
+count=0
+endif
+
+if upsecs%5==0
+then =\>print %upsecs% (every 5 seconds)
+endif
+
+; not recommended for reliable timers
+timer+=1
+if timer\>=5
+then =\>print 5 seconds over (may be)
+timer=0
+endif
+
+dimmer+=1
+if dimmer\>100
+then dimmer=0
+endif
+
+=\>dimmer %dimmer%
+=\>WebSend %url% dimmer %dimmer%
+
+; show on display
+dprec0
+=\>displaytext [c1l1f1s2p20] dimmer=%dimmer%
+
+=\>print %upsecs% %uptime% %time% %sunrise% %sunset% %tstamp%
+
+if time\>sunset
+and time< sunrise
+then
+; night time
+if pwr[1]==0
+then =\>power1 1
+endif
+else
+; day time
+if pwr[1]\>0
+then =\>power1 0
+endif
+endif
+
+; clr display on boot
+if boot\>0
+then =\>displaytext [z]
+endif
+
+; frost warning
+if temp<0
+and mt<=0
+then =#sendmail("frost alert")
+; alarm only every 5 minutes
+mt=300
+=>mp3track 2
+endif
+
+; var has been updated
+if upd[hello]>0
+then =>print %hello%
+endif
+
+; send to Thingspeak every 60 seconds
+; average data in between
+if upsecs%60==0
+then
+ahum/=tcnt
+atemp/=tcnt
+=>Websend [184.106.153.149:80]/update?key=PYUZMVWCICBW492&field1=%atemp%&field2=%ahum%
+tcnt=0
+atemp=0
+ahum=0
+else
+ahum+=hum
+atemp+=temp
+tcnt+=1
+endif
+
+hour=int(time/60)
+if chg[hour]>0
+then
+; exactly every hour
+=>print full hour reached
+endif
+
+if time>5 {
+=>print more then 5 minutes after midnight
+} else {
+=>print less then 5 minutes after midnight
+}
+
+
+; publish abs hum every teleperiod time
+if mqtts>0
+and upsecs%tper==0
+then
+; calc abs humidity
+tmp=pow(2.718281828 (17.67\*temp)/(temp+243.5))
+tmp=(6.112\*tmp\*hum\*18.01534)/((273.15+temp)\*8.31447215)
+; publish median filtered value
+=>Publish tele/%topic%/SENSOR {"Script":{"abshum":%med(0 tmp)%}}
+endif
+
+;switch case state machine
+switch state
+case 1
+=>print state=%state% , start
+state+=1
+case 2
+=>print state=%state%
+state+=1
+case 3
+=>print state=%state% , reset
+state=1
+ends
+
+
+; subroutines
+\#sub1(string)
+=>print sub1: %string%
+\#sub2(param)
+=>print sub2: %param%
+
+\#sendmail(string)
+=>sendmail [smtp.gmail.com:465:user:passwd:::alarm] %string%
+
+**\>E**
+=\>print event executed!
+
+
+; check if switch changed state
+sw=sw[1]
+if chg[sw]>0
+then =\>power1 %sw%
+endif
+
+hello="event occured"
+
+; check for Color change (Color is a string)
+col=Color
+; color change needs 2 string vars
+if col!=ocol
+then ocol=col
+=>print color changed %col%
+endif
+
+; or check change of color channels
+chan1=Channel[1]
+chan2=Channel[2]
+chan3=Channel[3]
+
+if chg[chan1]>0
+or chg[chan2]>0
+or chg[chan3]>0
+then => color has changed
+endif
+
+; compose color string for red
+col=hn(255)+hn(0)+hn(0)
+=>color %col%
+
+**\>R**
+=\>print restarting now
+
+**a real example**
+epaper 29 with sgp30 and bme280
+some vars are set from iobroker
+DisplayText substituted to save script space
+\>D
+hum=0
+temp=0
+press=0
+ahum=0
+tvoc=0
+eco2=0
+zwz=0
+wr1=0
+wr2=0
+wr3=0
+otmp=0
+pwl=0
+tmp=0
+DT="DisplayText"
+; preset units in case they are not available
+punit="hPa"
+tunit="C"
+
+\>B
+;reset auto draw
+=>%DT% [zD0]
+;clr display and draw a frame
+=>%DT% [x0y20h296x0y40h296]
+
+\>T
+; get tele vars
+temp=BME280#Temperature
+hum=BME280#Humidity
+press=BME280#Pressure
+tvoc=SGP30#TVOC
+eco2=SGP30#eCO2
+ahum=SGP30#aHumidity
+tunit=TempUnit
+punit=PressureUnit
+
+\>S
+// update display every teleperiod time
+if upsecs%tper==0
+then
+dprec2
+=>%DT% [f1p7x0y5]%temp% %tunit%
+=>%DT% [p5x70y5]%hum% %%[x250y5t]
+=>%DT% [p11x140y5]%press% %punit%
+=>%DT% [p10x30y25]TVOC: %tvoc% ppb
+=>%DT% [p10x160y25]eCO2: %eco2% ppm
+=>%DT% [p10c26l5]ahum: %ahum% g^m3
+
+dprec0
+=>%DT% [p25c1l5]WR 1 (Dach) : %wr1% W
+=>%DT% [p25c1l6]WR 2 (Garage): %-wr3% W
+=>%DT% [p25c1l7]WR 3 (Garten): %-wr2% W
+=>%DT% [p25c1l8]Aussentemperatur: %otmp% C
+=>%DT% [x170y95r120:30f2p6x185y100] %pwl% %%
+; now update screen
+=>%DT% [d]
+endif
+
+
+\>E
+
+\>R
+
+**another real example**
+ILI 9488 color LCD Display shows various energy graphs
+display switches on and off with proximity sensor
+BMP280 and vl5310x
+some vars are set from iobroker
+
+**>D**
+temp=0
+press=0
+zwz=0
+wr1=0
+wr2=0
+wr3=0
+otmp=0
+pwl=0
+tmp=0
+dist=0
+DT="DisplayText"
+punit="hPa"
+tunit="C"
+hour=0
+
+**>B**
+=>%DT% [z]
+
+// define 2 graphs, 2. has 3 tracks
+=>%DT% [zCi1G2656:5:20:400:80:1440:-5000:5000:3Ci7f3x410y20]+5000 W[x410y95]-5000 W [Ci7f1x70y3] Zweirichtungsz~80hler - 24 Stunden
+=>%DT% [Ci1G2657:5:120:400:80:1440:0:5000:3Ci7f3x410y120]+5000 W[x410y195]0 W [Ci7f1x70y103] Wechselrichter 1-3 - 24 Stunden
+=>%DT% [Ci1G2658:5:120:400:80:1440:0:5000:16][Ci1G2659:5:120:400:80:1440:0:5000:5]
+=>%DT% [f1s1b0:260:260:100:50:2:11:4:2:Rel 1:b1:370:260:100:50:2:11:4:2:Dsp off:]
+=>mp3volume 100
+=>mp3track 4
+
+**>T**
+; get some tele vars
+temp=BMP280#Temperature
+press=BMP280#Pressure
+tunit=TempUnit
+punit=PressureUnit
+dist=VL53L0X#Distance
+
+; check proximity sensor to switch display on/off
+; to prevent burn in
+if dist>300
+then
+if pwr[2]>0
+then
+=>power2 0
+endif
+else
+if pwr[2]==0
+then
+=>power2 1
+endif
+endif
+
+
+**>S**
+; update graph every teleperiod
+if upsecs%tper==0
+then
+dprec2
+=>%DT% [f1Ci3x40y260w30Ci1]
+=>%DT% [Ci7x120y220t]
+=>%DT% [Ci7x180y220T]
+=>%DT% [Ci7p8x120y240]%temp% %tunit%
+=>%DT% [Ci7x120y260]%press% %punit%
+=>%DT% [Ci7x120y280]%dist% mm
+dprec0
+=>%DT% [g0:%zwz%g1:%wr1%g2:%-wr2%g3:%-wr3%]
+if zwz>0
+then
+=>%DT% [p-8x410y55Ci2Bi0]%zwz% W
+else
+=>%DT% [p-8x410y55Ci3Bi0]%zwz% W
+endif
+=>%DT% [p-8x410y140Ci3Bi0]%wr1% W
+=>%DT% [p-8x410y155Ci16Bi0]%-wr2% W
+=>%DT% [p-8x410y170Ci5Bi0]%-wr3% W
+endif
+
+; chime every full hour
+hour=int(time/60)
+if chg[hour]>0
+then =>mp3track 4
+endif
+
+**>E**
+
+**>R**
diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino
index 6e1260c78..9517755ee 100755
--- a/sonoff/sonoff.ino
+++ b/sonoff/sonoff.ino
@@ -1468,7 +1468,12 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len)
Response_P(PSTR("{\"" D_JSON_COMMAND "\":\"" D_JSON_UNKNOWN "\"}"));
type = (char*)topicBuf;
}
- if (mqtt_data[0] != '\0') { MqttPublishPrefixTopic_P(RESULT_OR_STAT, type); }
+ if (mqtt_data[0] != '\0') {
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, type);
+#ifdef USE_SCRIPT
+ XdrvRulesProcess();
+#endif
+ }
fallback_topic_flag = false;
}
@@ -1835,6 +1840,9 @@ void MqttPublishTeleState(void)
mqtt_data[0] = '\0';
MqttShowState();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
+#ifdef USE_SCRIPT
+ RulesTeleperiod(); // Allow rule based HA messages
+#endif // USE_SCRIPT
}
bool MqttShowSensor(void)
@@ -1919,7 +1927,7 @@ void PerformEverySecond(void)
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
-#ifdef USE_RULES
+#if defined(USE_RULES) || defined(USE_SCRIPT)
RulesTeleperiod(); // Allow rule based HA messages
#endif // USE_RULES
}
diff --git a/sonoff/support_features.ino b/sonoff/support_features.ino
index c97f77c9d..1802f93b4 100644
--- a/sonoff/support_features.ino
+++ b/sonoff/support_features.ino
@@ -103,7 +103,7 @@ void GetFeatures(void)
#ifdef USE_TIMERS_WEB
feature_drv1 |= 0x04000000; // xdrv_09_timers.ino
#endif
-#ifdef USE_RULES
+#if defined(USE_RULES) || defined(USE_SCRIPT)
feature_drv1 |= 0x08000000; // xdrv_10_rules.ino
#endif
#ifdef USE_KNX
diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino
index dcd18c6f1..3f838ed7a 100644
--- a/sonoff/xdrv_09_timers.ino
+++ b/sonoff/xdrv_09_timers.ino
@@ -285,7 +285,7 @@ void TimerEverySecond(void)
if (time == set_time) {
if (xtimer.days & days) {
Settings.timer[i].arm = xtimer.repeat;
-#ifdef USE_RULES
+#if defined(USE_RULES) || defined(USE_SCRIPT)
if (3 == xtimer.power) { // Blink becomes Rule disregarding device and allowing use of Backlog commands
Response_P(PSTR("{\"Clock\":{\"Timer\":%d}}"), i +1);
XdrvRulesProcess();
@@ -359,7 +359,8 @@ bool TimerCommand(void)
Settings.timer[index -1].data = Settings.timer[XdrvMailbox.payload -1].data; // Copy timer
}
} else {
-#ifndef USE_RULES
+//#ifndef USE_RULES
+#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
if (devices_present) {
#endif
StaticJsonBuffer<256> jsonBuffer;
@@ -437,7 +438,8 @@ bool TimerCommand(void)
index++;
}
-#ifndef USE_RULES
+//#ifndef USE_RULES
+#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
} else {
Response_P(PSTR("{\"" D_CMND_TIMER "%d\":\"" D_JSON_TIMER_NO_DEVICE "\"}"), index); // No outputs defined so nothing to control
error = 1;
@@ -630,7 +632,7 @@ const char HTTP_TIMER_SCRIPT5[] PROGMEM =
"if(%d>0){" // Create Output and Action drop down boxes
"eb('oa').innerHTML=\"" D_TIMER_OUTPUT " " D_TIMER_ACTION " \";"
"o=qs('#p1');ce('" D_OFF "',o);ce('" D_ON "',o);ce('" D_TOGGLE "',o);" // Create offset direction select options
-#ifdef USE_RULES
+#if defined(USE_RULES) || defined(USE_SCRIPT)
"ce('" D_RULE "',o);"
#else
"ce('" D_BLINK "',o);"
@@ -768,7 +770,7 @@ bool Xdrv09(uint8_t function)
#ifdef USE_WEBSERVER
#ifdef USE_TIMERS_WEB
case FUNC_WEB_ADD_BUTTON:
-#ifdef USE_RULES
+#if defined(USE_RULES) || defined(USE_SCRIPT)
WSContentSend_P(HTTP_BTN_MENU_TIMER);
#else
if (devices_present) { WSContentSend_P(HTTP_BTN_MENU_TIMER); }
diff --git a/sonoff/xdrv_10_scripter.ino b/sonoff/xdrv_10_scripter.ino
new file mode 100644
index 000000000..72e6b6190
--- /dev/null
+++ b/sonoff/xdrv_10_scripter.ino
@@ -0,0 +1,2235 @@
+/*
+ xdrv_10_scripter.ino - script support for Sonoff-Tasmota
+
+ Copyright (C) 2019 Gerhard Mutz and Theo Arends
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+// for doku see up to date doku in file scripter.md
+
+// uses about 14,2 k of flash
+// more stack could be needed for sendmail => -D CONT_STACKSIZE=4800 = +0.8k stack -0.8k heap
+//
+
+/* to doo
+optimize code for space
+
+// remarks
+goal is fast execution time, minimal use of ram and intuitive syntax
+therefore =>
+case sensitive cmds and vars (lowercase uses time and code)
+no math hierarchy (costs ram and execution time, better group with brackets, anyhow better readable for beginners)
+(will probably make math hierarchy an ifdefed option)
+keywords if then else endif, or, and are better readable for beginners (others may use {})
+*/
+#ifdef USE_SCRIPT
+#ifndef USE_RULES
+
+#define XDRV_10 10
+
+#define SCRIPT_DEBUG 0
+
+#define MAXVARS 50
+#define MAXNVARS 45
+#define MAXSVARS 5
+#define MAXFILT 5
+#define SCRIPT_SVARSIZE 20
+#define SCRIPT_MAXSSIZE 48
+#define SCRIPT_EOL '\n'
+#define SCRIPT_FLOAT_PRECISION 2
+#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-4/sizeof(float)
+
+
+enum {OPER_EQU=1,OPER_PLS,OPER_MIN,OPER_MUL,OPER_DIV,OPER_PLSEQU,OPER_MINEQU,OPER_MULEQU,OPER_DIVEQU,OPER_EQUEQU,OPER_NOTEQU,OPER_GRTEQU,OPER_LOWEQU,OPER_GRT,OPER_LOW,OPER_PERC,OPER_XOR,OPER_AND,OPER_OR,OPER_ANDEQU,OPER_OREQU,OPER_XOREQU,OPER_PERCEQU};
+enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD};
+
+typedef union {
+ uint8_t data;
+ struct {
+ uint8_t is_string : 1; // string or number
+ uint8_t is_permanent : 1;
+ uint8_t is_timer : 1;
+ uint8_t is_autoinc : 1;
+ uint8_t changed : 1;
+ uint8_t settable : 1;
+ uint8_t is_filter : 1;
+ uint8_t constant : 1;
+ };
+} SCRIPT_TYPE;
+
+struct T_INDEX {
+ uint8_t index;
+ SCRIPT_TYPE bits;
+};
+
+struct M_FILT {
+ uint8_t numvals;
+ uint8_t index;
+ float maccu;
+ float rbuff[1];
+};
+
+// global memory
+struct SCRIPT_MEM {
+ float *fvars; // number var pointer
+ float *s_fvars; // shadow var pointer
+ struct T_INDEX *type; // type and index pointer
+ struct M_FILT *mfilt;
+ char *glob_vnp; // var name pointer
+ uint8_t *vnp_offset;
+ char *glob_snp; // string vars pointer
+ char *scriptptr;
+ uint8_t numvars;
+ void *script_mem;
+ uint16_t script_mem_size;
+ uint8_t script_dprec;
+ uint8_t var_not_found;
+ uint8_t glob_error;
+ uint8_t max_ssize;
+ uint8_t script_loglevel;
+} glob_script_mem;
+
+uint8_t tasm_cmd_activ=0;
+
+uint32_t script_lastmillis;
+
+char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo);
+
+void ScriptEverySecond(void) {
+
+ if (bitRead(Settings.rule_enabled, 0)) {
+ struct T_INDEX *vtp=glob_script_mem.type;
+ float delta=(millis()-script_lastmillis)/1000;
+ script_lastmillis=millis();
+ for (uint8_t count=0; count0) {
+ // decrement
+ *fp-=delta;
+ if (*fp<0) *fp=0;
+ }
+ }
+ if (vtp[count].bits.is_autoinc) {
+ // increments timers
+ float *fp=&glob_script_mem.fvars[vtp[count].index];
+ if (*fp>=0) {
+ *fp+=delta;
+ }
+ }
+ }
+ Run_Scripter(">S",2,0);
+ }
+}
+
+void RulesTeleperiod(void) {
+ if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">T",2, mqtt_data);
+}
+
+#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++;
+#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++;
+
+// allocates all variable and presets them
+int16_t Init_Scripter(char *script) {
+ // scan lines for >DEF
+ uint16_t lines=0,nvars=0,svars=0,vars=0;
+ char *lp=script;
+ char vnames[MAXVARS*10];
+ char *vnames_p=vnames;
+ char *vnp[MAXVARS];
+ char **vnp_p=vnp;
+ char strings[MAXSVARS*SCRIPT_MAXSSIZE];
+ struct M_FILT mfilt[MAXFILT];
+
+ char *strings_p=strings;
+ char *snp[MAXSVARS];
+ char **snp_p=snp;
+ uint8_t numperm=0,numflt=0,count;
+
+ glob_script_mem.max_ssize=SCRIPT_SVARSIZE;
+ glob_script_mem.scriptptr=0;
+ if (!*script) return -999;
+
+ float fvalues[MAXVARS];
+ struct T_INDEX vtypes[MAXVARS];
+ char init=0;
+ while (1) {
+ // check line
+ // skip leading spaces
+ SCRIPT_SKIP_SPACES
+ // skip empty line
+ if (*lp=='\n' || *lp=='\r') goto next_line;
+ // skip comment
+ if (*lp==';') goto next_line;
+ if (init) {
+ // init section
+ if (*lp=='>') {
+ init=0;
+ break;
+ }
+ char *op=strchr(lp,'=');
+ if (op) {
+ vtypes[vars].bits.data=0;
+ // found variable definition
+ if (*lp=='p' && *(lp+1)==':') {
+ lp+=2;
+ if (numpermMAXFILT) {
+ return -6;
+ }
+ } else {
+ vtypes[vars].bits.is_filter=0;
+ }
+ *vnp_p++=vnames_p;
+ while (lpMAXNVARS) {
+ return -1;
+ }
+ } else {
+ // string vars
+ op++;
+ *snp_p++=strings_p;
+ while (*op!='\"') {
+ if (*op==SCRIPT_EOL) break;
+ *strings_p++=*op++;
+ }
+ *strings_p++=0;
+ vtypes[vars].bits.is_string=1;
+ vtypes[vars].index=svars;
+ svars++;
+ if (svars>MAXSVARS) {
+ return -2;
+ }
+ }
+ vars++;
+ if (vars>MAXVARS) {
+ return -3;
+ }
+ }
+ } else {
+ if (!strncmp(lp,">D",2)) {
+ lp+=2;
+ SCRIPT_SKIP_SPACES
+ if (isdigit(*lp)) {
+ uint8_t ssize=atoi(lp)+1;
+ if (ssize<10 || ssize>SCRIPT_MAXSSIZE) ssize=SCRIPT_MAXSSIZE;
+ glob_script_mem.max_ssize=ssize;
+ }
+ init=1;
+ }
+ }
+ // next line
+ next_line:
+ lp = strchr(lp, SCRIPT_EOL);
+ if (!lp) break;
+ lp++;
+ }
+
+ uint16_t fsize=0;
+ for (count=0; countnumvals=mfilt[count].numvals;
+ mp+=sizeof(struct M_FILT)+((mfilt[count].numvals&0x7f)-1)*sizeof(float);
+ }
+
+ glob_script_mem.numvars=vars;
+ glob_script_mem.script_dprec=SCRIPT_FLOAT_PRECISION;
+ glob_script_mem.script_loglevel=LOG_LEVEL_INFO;
+
+
+#if SCRIPT_DEBUG>2
+ struct T_INDEX *dvtp=glob_script_mem.type;
+ for (uint8_t count=0; count0
+ ClaimSerial();
+ SetSerialBaudrate(9600);
+#endif
+
+ // store start of actual program here
+ glob_script_mem.scriptptr=lp-1;
+ return 0;
+
+}
+
+#define NUM_RES 0xfe
+#define STR_RES 0xfd
+#define VAR_NV 0xff
+
+#define NTYPE 0
+#define STYPE 0x80
+
+
+//Settings.seriallog_level
+//Settings.weblog_level
+
+float Get_MFilter(uint8_t index) {
+ uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
+ for (uint8_t count=0; countnumvals&0x80) {
+ // moving average
+ return mflp->maccu/(mflp->numvals&0x7f);
+ } else {
+ // median, sort array
+ float tbuff[mflp->numvals],tmp;
+ uint8_t flag;
+ memmove(tbuff,mflp->rbuff,sizeof(tbuff));
+ for (uint8_t ocnt=0; ocntnumvals; ocnt++) {
+ flag=0;
+ for (uint8_t count=0; countnumvals-1; count++) {
+ if (tbuff[count]>tbuff[count+1]) {
+ tmp=tbuff[count];
+ tbuff[count]=tbuff[count+1];
+ tbuff[count+1]=tmp;
+ flag=1;
+ }
+ }
+ if (!flag) break;
+ }
+ return mflp->rbuff[mflp->numvals/2];
+ }
+ }
+ mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
+ }
+ return 0;
+}
+
+void Set_MFilter(uint8_t index, float invar) {
+ uint8_t *mp=(uint8_t*)glob_script_mem.mfilt;
+ for (uint8_t count=0; countnumvals&0x80) {
+ // moving average
+ mflp->maccu-=mflp->rbuff[mflp->index];
+ mflp->maccu+=invar;
+ mflp->rbuff[mflp->index]=invar;
+ mflp->index++;
+ if (mflp->index>=(mflp->numvals&0x7f)) mflp->index=0;
+ } else {
+ // median
+ mflp->rbuff[mflp->index]=invar;
+ mflp->index++;
+ if (mflp->index>=mflp->numvals) mflp->index=0;
+ }
+ break;
+ }
+ mp+=sizeof(struct M_FILT)+((mflp->numvals&0x7f)-1)*sizeof(float);
+ }
+}
+
+#define MEDIAN_SIZE 5
+#define MEDIAN_FILTER_NUM 2
+
+struct MEDIAN_FILTER {
+float buffer[MEDIAN_SIZE];
+int8_t index;
+} script_mf[MEDIAN_FILTER_NUM];
+
+float DoMedian5(uint8_t index, float in) {
+
+ if (index>=MEDIAN_FILTER_NUM) index=0;
+
+ struct MEDIAN_FILTER* mf=&script_mf[index];
+
+ float tbuff[MEDIAN_SIZE],tmp;
+ uint8_t flag;
+ mf->buffer[mf->index]=in;
+ mf->index++;
+ if (mf->index>=MEDIAN_SIZE) mf->index=0;
+ // sort list and take median
+ memmove(tbuff,mf->buffer,sizeof(tbuff));
+ for (uint8_t ocnt=0; ocnttbuff[count+1]) {
+ tmp=tbuff[count];
+ tbuff[count]=tbuff[count+1];
+ tbuff[count+1]=tmp;
+ flag=1;
+ }
+ }
+ if (!flag) break;
+ }
+ return tbuff[MEDIAN_SIZE/2];
+}
+
+
+// vtype => ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number
+// no flash strings here for performance reasons!!!
+char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) {
+ uint16_t count,len=0;
+ uint8_t nres=0;
+ char vname[32];
+ float fvar=0;
+ tind->index=0;
+ tind->bits.data=0;
+
+ if (isdigit(*lp) || (*lp=='-' && isdigit(*(lp+1))) || *lp=='.') {
+ // isnumber
+ if (fp) *fp=CharToDouble(lp);
+ if (*lp=='-') lp++;
+ while (isdigit(*lp) || *lp=='.') {
+ if (*lp==0 || *lp==SCRIPT_EOL) break;
+ lp++;
+ }
+ tind->bits.constant=1;
+ tind->bits.is_string=0;
+ *vtype=NUM_RES;
+ return lp;
+ }
+ if (*lp=='"') {
+ lp++;
+ while (*lp!='"') {
+ if (*lp==0 || *lp==SCRIPT_EOL) break;
+ if (sp) *sp++=*lp;
+ lp++;
+ }
+ if (sp) *sp=0;
+ *vtype=STR_RES;
+ tind->bits.constant=1;
+ tind->bits.is_string=1;
+ return lp+1;
+ }
+
+ if (*lp=='-') {
+ // inverted var
+ nres=1;
+ lp++;
+ }
+
+ const char *term="\n\r ])=+-/*%>index=VAR_NV;
+ glob_script_mem.var_not_found=1;
+ return lp;
+ }
+
+ struct T_INDEX *vtp=glob_script_mem.type;
+ for (count=0; countindex=count; // overwrite with global var index
+ if (vtp[count].bits.is_string==0) {
+ *vtype=NTYPE|index;
+ if (vtp[count].bits.is_filter) {
+ fvar=Get_MFilter(index);
+ } else {
+ fvar=glob_script_mem.fvars[index];
+ }
+ if (nres) fvar=-fvar;
+ if (fp) *fp=fvar;
+ } else {
+ *vtype=STYPE|index;
+ if (sp) strlcpy(sp,glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize),SCRIPT_MAXSSIZE);
+ }
+ return lp+len;
+ }
+ }
+ }
+
+ if (jo) {
+ // look for json input
+ const char* str_value;
+ uint8_t aindex;
+ String vn;
+ char *ja=strchr(vname,'[');
+ if (ja) {
+ // json array
+ *ja=0;
+ ja++;
+ // fetch array index
+ float fvar;
+ GetNumericResult(ja,OPER_EQU,&fvar,0);
+ aindex=fvar;
+ if (aindex<1 || aindex>6) aindex=1;
+ aindex--;
+ }
+ if (jo->success()) {
+ char *subtype=strchr(vname,'#');
+ if (subtype) {
+ *subtype=0;
+ subtype++;
+ }
+ vn=vname;
+ str_value = (*jo)[vn];
+ if ((*jo)[vn].success()) {
+ if (subtype) {
+ JsonObject &jobj1=(*jo)[vn];
+ if (jobj1.success()) {
+ vn=subtype;
+ jo=&jobj1;
+ str_value = (*jo)[vn];
+ if ((*jo)[vn].success()) {
+ goto skip;
+ }
+ } else {
+ goto chknext;
+ }
+ }
+ skip:
+ if (ja) {
+ // json array
+ str_value = (*jo)[vn][aindex];
+ }
+ if (str_value && *str_value) {
+ if ((*jo).is(vn)) {
+ if (!strncmp(str_value,"ON",2)) {
+ if (fp) *fp=1;
+ } else if (!strncmp(str_value,"OFF",3)) {
+ if (fp) *fp=0;
+ } else {
+ *vtype=STR_RES;
+ tind->bits.constant=1;
+ tind->bits.is_string=1;
+ if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE);
+ return lp+len;
+ }
+ } else {
+ if (fp) *fp=CharToDouble((char*)str_value);
+ *vtype=NUM_RES;
+ tind->bits.constant=1;
+ tind->bits.is_string=0;
+ return lp+len;
+ }
+ }
+ }
+ }
+ }
+
+chknext:
+ switch (vname[0]) {
+ case 'b':
+ if (!strncmp(vname,"boot",4)) {
+ if (rules_flag.system_boot) {
+ rules_flag.system_boot=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ break;
+ case 'c':
+ if (!strncmp(vname,"chg[",4)) {
+ // var changed
+ struct T_INDEX ind;
+ uint8_t vtype;
+ isvar(vname+4,&vtype,&ind,0,0,0);
+ if (!ind.bits.constant) {
+ uint8_t index=glob_script_mem.type[ind.index].index;
+ if (glob_script_mem.fvars[index]!=glob_script_mem.s_fvars[index]) {
+ // var has changed
+ glob_script_mem.s_fvars[index]=glob_script_mem.fvars[index];
+ fvar=1;
+ len++;
+ goto exit;
+ } else {
+ fvar=0;
+ len++;
+ goto exit;
+ }
+ }
+ }
+ break;
+ case 'd':
+ if (!strncmp(vname,"day",3)) {
+ fvar=RtcTime.day_of_month;
+ goto exit;
+ }
+ break;
+ case 'g':
+ if (!strncmp(vname,"gtmp",4)) {
+ fvar=global_temperature;
+ goto exit;
+ }
+ if (!strncmp(vname,"ghum",4)) {
+ fvar=global_humidity;
+ goto exit;
+ }
+ if (!strncmp(vname,"gprs",4)) {
+ fvar=global_pressure;
+ goto exit;
+ }
+ if (!strncmp(vname,"gtopic",6)) {
+ if (sp) strlcpy(sp,Settings.mqtt_grptopic,glob_script_mem.max_ssize);
+ goto strexit;
+ }
+ break;
+ case 'h':
+ if (!strncmp(vname,"hours",5)) {
+ fvar=RtcTime.hour;
+ goto exit;
+ }
+ if (!strncmp(vname,"heap",4)) {
+ fvar=ESP.getFreeHeap();
+ goto exit;
+ }
+ if (!strncmp(vname,"hn(",3)) {
+ lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0);
+ if (fvar<0 || fvar>255) fvar=0;
+ lp++;
+ len=0;
+ if (sp) {
+ sprintf(sp,"%02x",(uint8_t)fvar);
+ }
+ goto strexit;
+ }
+ break;
+ case 'i':
+ if (!strncmp(vname,"int(",4)) {
+ lp=GetNumericResult(lp+4,OPER_EQU,&fvar,0);
+ fvar=floor(fvar);
+ lp++;
+ len=0;
+ goto exit;
+ }
+ break;
+ case 'l':
+ if (!strncmp(vname,"loglvl",6)) {
+ fvar=glob_script_mem.script_loglevel;
+ tind->index=SCRIPT_LOGLEVEL;
+ exit_settable:
+ if (fp) *fp=fvar;
+ *vtype=NTYPE;
+ tind->bits.settable=1;
+ tind->bits.is_string=0;
+ return lp+len;
+ }
+ break;
+ case 'm':
+ if (!strncmp(vname,"med(",4)) {
+ float fvar1;
+ lp=GetNumericResult(lp+4,OPER_EQU,&fvar1,0);
+ SCRIPT_SKIP_SPACES
+ // arg2
+ float fvar2;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
+ fvar=DoMedian5(fvar1,fvar2);
+ lp++;
+ len=0;
+ goto exit;
+ }
+ if (!strncmp(vname,"micros",6)) {
+ fvar=micros();
+ goto exit;
+ }
+ if (!strncmp(vname,"millis",6)) {
+ fvar=millis();
+ goto exit;
+ }
+ if (!strncmp(vname,"mins",4)) {
+ fvar=RtcTime.minute;
+ goto exit;
+ }
+ if (!strncmp(vname,"month",5)) {
+ fvar=RtcTime.month;
+ goto exit;
+ }
+ if (!strncmp(vname,"mqttc",5)) {
+ if (rules_flag.mqtt_connected) {
+ rules_flag.mqtt_connected=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"mqttd",5)) {
+ if (rules_flag.mqtt_disconnected) {
+ rules_flag.mqtt_disconnected=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"mqtts",5)) {
+ fvar=!global_state.mqtt_down;
+ goto exit;
+ }
+ break;
+ case 'p':
+ if (!strncmp(vname,"pin[",4)) {
+ // raw pin level
+ GetNumericResult(vname+4,OPER_EQU,&fvar,0);
+ fvar=digitalRead((uint8_t)fvar);
+ // skip ] bracket
+ len++;
+ goto exit;
+ }
+ if (!strncmp(vname,"pn[",3)) {
+ GetNumericResult(vname+3,OPER_EQU,&fvar,0);
+ fvar=pin[(uint8_t)fvar];
+ // skip ] bracket
+ len++;
+ goto exit;
+ }
+ if (!strncmp(vname,"pd[",3)) {
+ GetNumericResult(vname+3,OPER_EQU,&fvar,0);
+ uint8_t gpiopin=fvar;
+ for (uint8_t i=0;iindex=SCRIPT_TELEPERIOD;
+ goto exit_settable;
+ }
+ if (!strncmp(vname,"tinit",5)) {
+ if (rules_flag.time_init) {
+ rules_flag.time_init=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"tset",4)) {
+ if (rules_flag.time_set) {
+ rules_flag.time_set=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"tstamp",6)) {
+ if (sp) strlcpy(sp,GetDateAndTime(DT_LOCAL).c_str(),glob_script_mem.max_ssize);
+ goto strexit;
+ }
+ if (!strncmp(vname,"topic",5)) {
+ if (sp) strlcpy(sp,Settings.mqtt_topic,glob_script_mem.max_ssize);
+ goto strexit;
+ }
+ break;
+ case 'u':
+ if (!strncmp(vname,"uptime",6)) {
+ fvar=MinutesUptime();
+ goto exit;
+ }
+ if (!strncmp(vname,"upsecs",6)) {
+ fvar=uptime;
+ goto exit;
+ }
+ if (!strncmp(vname,"upd[",4)) {
+ // var was updated
+ struct T_INDEX ind;
+ uint8_t vtype;
+ isvar(vname+4,&vtype,&ind,0,0,0);
+ if (!ind.bits.constant) {
+ if (!ind.bits.changed) {
+ fvar=0;
+ len++;
+ goto exit;
+ } else {
+ glob_script_mem.type[ind.index].bits.changed=0;
+ fvar=1;
+ len++;
+ goto exit;
+ }
+ }
+ goto notfound;
+ }
+ break;
+ case 'w':
+ if (!strncmp(vname,"wday",4)) {
+ fvar=RtcTime.day_of_week;
+ goto exit;
+ }
+ if (!strncmp(vname,"wific",5)) {
+ if (rules_flag.wifi_connected) {
+ rules_flag.wifi_connected=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"wifid",5)) {
+ if (rules_flag.wifi_disconnected) {
+ rules_flag.wifi_disconnected=0;
+ fvar=1;
+ }
+ goto exit;
+ }
+ if (!strncmp(vname,"wifis",5)) {
+ fvar=!global_state.wifi_down;
+ goto exit;
+ }
+ break;
+ case 'y':
+ if (!strncmp(vname,"year",4)) {
+ fvar=RtcTime.year;
+ goto exit;
+ }
+ break;
+ default:
+ break;
+ }
+ // nothing valid found
+notfound:
+ if (fp) *fp=0;
+ *vtype=VAR_NV;
+ tind->index=VAR_NV;
+ glob_script_mem.var_not_found=1;
+ return lp;
+ // return constant numbers
+exit:
+ if (fp) *fp=fvar;
+ *vtype=NUM_RES;
+ tind->bits.constant=1;
+ tind->bits.is_string=0;
+ return lp+len;
+ // return constant strings
+strexit:
+ *vtype=STYPE;
+ tind->bits.constant=1;
+ tind->bits.is_string=1;
+ return lp+len;
+}
+
+
+
+char *getop(char *lp, uint8_t *operand) {
+ switch (*lp) {
+ case '=':
+ if (*(lp+1)=='=') {
+ *operand=OPER_EQUEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_EQU;
+ return lp+1;
+ }
+ break;
+ case '+':
+ if (*(lp+1)=='=') {
+ *operand=OPER_PLSEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_PLS;
+ return lp+1;
+ }
+ break;
+ case '-':
+ if (*(lp+1)=='=') {
+ *operand=OPER_MINEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_MIN;
+ return lp+1;
+ }
+ break;
+ case '*':
+ if (*(lp+1)=='=') {
+ *operand=OPER_MULEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_MUL;
+ return lp+1;
+ }
+ break;
+ case '/':
+ if (*(lp+1)=='=') {
+ *operand=OPER_DIVEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_DIV;
+ return lp+1;
+ }
+ break;
+ case '!':
+ if (*(lp+1)=='=') {
+ *operand=OPER_NOTEQU;
+ return lp+2;
+ }
+ break;
+ case '>':
+ if (*(lp+1)=='=') {
+ *operand=OPER_GRTEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_GRT;
+ return lp+1;
+
+ }
+ break;
+ case '<':
+ if (*(lp+1)=='=') {
+ *operand=OPER_LOWEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_LOW;
+ return lp+1;
+ }
+ break;
+ case '%':
+ if (*(lp+1)=='=') {
+ *operand=OPER_PERCEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_PERC;
+ return lp+1;
+ }
+ break;
+ case '^':
+ if (*(lp+1)=='=') {
+ *operand=OPER_XOREQU;
+ return lp+2;
+ } else {
+ *operand=OPER_XOR;
+ return lp+1;
+ }
+ break;
+ case '&':
+ if (*(lp+1)=='=') {
+ *operand=OPER_ANDEQU;
+ return lp+2;
+ } else {
+ *operand=OPER_AND;
+ return lp+1;
+ }
+ break;
+ case '|':
+ if (*(lp+1)=='=') {
+ *operand=OPER_OREQU;
+ return lp+2;
+ } else {
+ *operand=OPER_OR;
+ return lp+1;
+ }
+ break;
+ }
+ *operand=0;
+ return lp;
+}
+
+
+#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1)
+// All version before core 2.4.2
+// https://github.com/esp8266/Arduino/issues/2557
+extern "C" {
+#include
+ extern cont_t g_cont;
+}
+uint16_t GetStack(void) {
+ register uint32_t *sp asm("a1");
+ return (4 * (sp - g_pcont.stack));
+}
+
+#else
+extern "C" {
+#include
+ extern cont_t* g_pcont;
+}
+uint16_t GetStack(void) {
+ register uint32_t *sp asm("a1");
+ return (4 * (sp - g_pcont->stack));
+}
+#endif
+
+char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo) {
+ uint8_t operand=0;
+ uint8_t vtype;
+ char *slp;
+ struct T_INDEX ind;
+ char str[SCRIPT_MAXSSIZE],str1[SCRIPT_MAXSSIZE];
+ while (1) {
+ lp=isvar(lp,&vtype,&ind,0,str1,jo);
+ switch (lastop) {
+ case OPER_EQU:
+ strlcpy(str,str1,sizeof(str));
+ break;
+ case OPER_PLS:
+ strlcat(str,str1,sizeof(str));
+ break;
+ }
+ slp=lp;
+ lp=getop(lp,&operand);
+ switch (operand) {
+ case OPER_EQUEQU:
+ case OPER_NOTEQU:
+ case OPER_LOW:
+ case OPER_LOWEQU:
+ case OPER_GRT:
+ case OPER_GRTEQU:
+ lp=slp;
+ strcpy(cp,str);
+ return lp;
+ break;
+ default:
+ break;
+ }
+ lastop=operand;
+ if (!operand) {
+ strcpy(cp,str);
+ return lp;
+ }
+ }
+}
+
+char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo) {
+uint8_t operand=0;
+float fvar1,fvar;
+char *slp;
+uint8_t vtype;
+struct T_INDEX ind;
+ while (1) {
+ // get 1. value
+ if (*lp=='(') {
+ lp++;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar1,jo);
+ lp++;
+ //if (*lp==')') lp++;
+ } else {
+ lp=isvar(lp,&vtype,&ind,&fvar1,0,jo);
+ if (vtype!=NUM_RES && vtype&STYPE) {
+ // string type
+ glob_script_mem.glob_error=1;
+ }
+ }
+ switch (lastop) {
+ case OPER_EQU:
+ fvar=fvar1;
+ break;
+ case OPER_PLS:
+ fvar+=fvar1;
+ break;
+ case OPER_MIN:
+ fvar-=fvar1;
+ break;
+ case OPER_MUL:
+ fvar*=fvar1;
+ break;
+ case OPER_DIV:
+ fvar/=fvar1;
+ break;
+ case OPER_PERC:
+ fvar=fmod(fvar,fvar1);
+ break;
+ case OPER_XOR:
+ fvar=(uint32_t)fvar^(uint32_t)fvar1;
+ break;
+ case OPER_AND:
+ fvar=(uint32_t)fvar&(uint32_t)fvar1;
+ break;
+ case OPER_OR:
+ fvar=(uint32_t)fvar|(uint32_t)fvar1;
+ break;
+ default:
+ break;
+ }
+ slp=lp;
+ lp=getop(lp,&operand);
+ switch (operand) {
+ case OPER_EQUEQU:
+ case OPER_NOTEQU:
+ case OPER_LOW:
+ case OPER_LOWEQU:
+ case OPER_GRT:
+ case OPER_GRTEQU:
+ lp=slp;
+ *fp=fvar;
+ return lp;
+ break;
+ default:
+ break;
+ }
+ lastop=operand;
+ if (!operand) {
+ *fp=fvar;
+ return lp;
+ }
+ }
+}
+
+
+// replace vars in cmd %var%
+void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
+ char *cp;
+ uint16_t count;
+ uint8_t vtype;
+ float fvar;
+ cp=srcbuf;
+ struct T_INDEX ind;
+ char string[SCRIPT_MAXSSIZE];
+ for (count=0;count=sizeof(str)) len=len>=sizeof(str);
+ strlcpy(str,cp,len);
+ toSLog(str);
+}
+
+void toLogEOL(const char *s1,const char *str) {
+ if (!str) return;
+ uint8_t index=0;
+ char *cp=log_data;
+ strcpy(cp,s1);
+ cp+=strlen(s1);
+ while (*str) {
+ if (*str==SCRIPT_EOL) break;
+ *cp++=*str++;
+ }
+ *cp=0;
+ AddLog(LOG_LEVEL_INFO);
+}
+
+
+void toSLog(const char *str) {
+ if (!str) return;
+#if SCRIPT_DEBUG>0
+ while (*str) {
+ Serial.write(*str);
+ str++;
+ }
+#endif
+}
+
+#define IF_NEST 8
+// execute section of scripter
+int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
+ uint8_t vtype=0,sindex,xflg,floop=0,globvindex;
+ struct T_INDEX ind;
+ uint8_t operand,lastop,numeric=1,if_state[IF_NEST],if_result[IF_NEST],and_or,ifstck=0,s_ifstck=0;
+ if_state[ifstck]=0;
+ if_result[ifstck]=0;
+ char cmpstr[SCRIPT_MAXSSIZE];
+
+ if (tasm_cmd_activ) return 0;
+
+
+ float *dfvar,*cv_count,cv_max,cv_inc;
+ char *cv_ptr;
+ float fvar=0,fvar1,sysvar,swvar;
+ uint8_t section=0,sysv_type=0,swflg=0;
+
+ if (!glob_script_mem.scriptptr) {
+ return -99;
+ }
+
+ DynamicJsonBuffer jsonBuffer; // on heap
+ JsonObject &jobj=jsonBuffer.parseObject(js);
+ JsonObject *jo;
+ if (js) jo=&jobj;
+ else jo=0;
+
+ char *lp=glob_script_mem.scriptptr;
+
+ while (1) {
+ // check line
+ // skip leading spaces
+ startline:
+ SCRIPT_SKIP_SPACES
+ // skip empty line
+ SCRIPT_SKIP_EOL
+ // skip comment
+ if (*lp==';') goto next_line;
+ if (!*lp) break;
+
+ if (section) {
+ // we are in section
+ if (*lp=='>') {
+ section=0;
+ break;
+ }
+ if (*lp=='#') {
+ section=0;
+ break;
+ }
+ glob_script_mem.var_not_found=0;
+
+#if SCRIPT_DEBUG>0
+ char tbuff[128];
+ sprintf(tbuff,"stack=%d,state=%d,cmpres=%d line: ",ifstck,if_state[ifstck],if_result[ifstck]);
+ toLogEOL(tbuff,lp);
+#endif
+
+ if (!strncmp(lp,"if",2)) {
+ lp+=2;
+ if (if_state[ifstck]>0) {
+ if (ifstck=2) {
+ lp+=5;
+ if_state[ifstck]=0;
+ if (ifstck>0) {
+ ifstck--;
+ }
+ s_ifstck=ifstck; // >>>>>
+ goto next_line;
+ } else if (!strncmp(lp,"or",2) && if_state[ifstck]==1) {
+ lp+=2;
+ and_or=1;
+ } else if (!strncmp(lp,"and",3) && if_state[ifstck]==1) {
+ lp+=3;
+ and_or=2;
+ }
+
+ if (*lp=='{' && if_state[ifstck]==1) {
+ lp+=1; // then
+ if_state[ifstck]=2;
+ } else if (*lp=='{' && if_state[ifstck]==3) {
+ lp+=1; // after else
+ //if_state[ifstck]=3;
+ } else if (*lp=='}' && if_state[ifstck]>=2) {
+ lp++; // must check for else
+ char *slp=lp;
+ uint8_t iselse=0;
+ for (uint8_t count=0; count<8;count++) {
+ if (*lp=='}') {
+ // must be endif
+ break;
+ }
+ if (!strncmp(lp,"else",4)) {
+ // is before else, no endif
+ if_state[ifstck]=3;
+ lp+=4;
+ iselse=1;
+ break;
+ }
+ lp++;
+ }
+ if (!iselse) {
+ lp=slp;
+ // endif
+ if_state[ifstck]=0;
+ if (ifstck>0) {
+ ifstck--;
+ }
+ s_ifstck=ifstck; // >>>>>
+ }
+ }
+
+ if (!strncmp(lp,"for",3)) {
+ // start for next loop, fetch 3 params
+ // simple implementation, zero loop count not supported
+ lp+=3;
+ SCRIPT_SKIP_SPACES
+ lp=isvar(lp,&vtype,&ind,0,0,0);
+ if ((vtype!=VAR_NV) && (vtype&STYPE)==0) {
+ // numeric var
+ uint8_t index=glob_script_mem.type[ind.index].index;
+ cv_count=&glob_script_mem.fvars[index];
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,cv_count,0);
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,&cv_max,0);
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,&cv_inc,0);
+ SCRIPT_SKIP_EOL
+ cv_ptr=lp;
+ floop=1;
+ } else {
+ // error
+ toLogEOL("for error",lp);
+ }
+ } else if (!strncmp(lp,"next",4) && floop>0) {
+ // for next loop
+ *cv_count+=cv_inc;
+ if (*cv_count<=cv_max) {
+ lp=cv_ptr;
+ } else {
+ lp+=4;
+ floop=0;
+ }
+ }
+
+ if (!strncmp(lp,"switch",6)) {
+ lp+=6;
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,&swvar,0);
+ swflg=1;
+ } else if (!strncmp(lp,"case",4) && swflg>0) {
+ lp+=4;
+ SCRIPT_SKIP_SPACES
+ float cvar;
+ lp=GetNumericResult(lp,OPER_EQU,&cvar,0);
+ if (swvar!=cvar) {
+ swflg=2;
+ } else {
+ swflg=1;
+ }
+ } else if (!strncmp(lp,"ends",4) && swflg>0) {
+ lp+=4;
+ swflg=0;
+ }
+
+ if (swflg==2) goto next_line;
+
+
+ SCRIPT_SKIP_SPACES
+ //SCRIPT_SKIP_EOL
+ if (*lp==SCRIPT_EOL) {
+ goto next_line;
+ }
+ //toLogN(lp,16);
+ if (if_state[s_ifstck]==3 && if_result[s_ifstck]) goto next_line;
+ if (if_state[s_ifstck]==2 && !if_result[s_ifstck]) goto next_line;
+
+ s_ifstck=ifstck;
+
+ if (!strncmp(lp,"break",5)) {
+ if (floop) {
+ // should break loop
+ floop=0;
+ } else {
+ section=0;
+ }
+ break;
+ } else if (!strncmp(lp,"dprec",5)) {
+ lp+=5;
+ // number precision
+ glob_script_mem.script_dprec=atoi(lp);
+ goto next_line;
+ } else if (!strncmp(lp,"delay(",6)) {
+ lp+=5;
+ // delay
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ delay(fvar);
+ goto next_line;
+ } else if (!strncmp(lp,"spinm(",6)) {
+ lp+=6;
+ // set pin mode
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ int8_t pinnr=fvar;
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ int8_t mode=fvar;
+ pinMode(pinnr,mode&1);
+ goto next_line;
+ } else if (!strncmp(lp,"spin(",5)) {
+ lp+=5;
+ // set pin mode
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ int8_t pinnr=fvar;
+ SCRIPT_SKIP_SPACES
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ int8_t mode=fvar;
+ digitalWrite(pinnr,mode&1);
+ goto next_line;
+ } else if (!strncmp(lp,"svars(",5)) {
+ lp+=5;
+ // save vars
+ Scripter_save_pvars();
+ goto next_line;
+ }
+
+ else if (!strncmp(lp,"=>",2)) {
+ // execute cmd
+ lp+=2;
+ SCRIPT_SKIP_SPACES
+ #define SCRIPT_CMDMEM 512
+ char *cmdmem=(char*)malloc(SCRIPT_CMDMEM);
+ if (cmdmem) {
+ char *cmd=cmdmem;
+ short count;
+ for (count=0; countfvar1);
+ break;
+ case OPER_GRTEQU:
+ res=(*dfvar>=fvar1);
+ break;
+ default:
+ // error
+ break;
+ }
+
+ if (!and_or) {
+ if_result[s_ifstck]=res;
+ } else if (and_or==1) {
+ if_result[s_ifstck]|=res;
+ } else {
+ if_result[s_ifstck]&=res;
+ }
+#if SCRIPT_DEBUG>0
+ char tbuff[128];
+ sprintf(tbuff,"p1=%d,p2=%d,cmpres=%d line: ",(int32_t)*dfvar,(int32_t)fvar1,if_result[s_ifstck]);
+ toLogEOL(tbuff,lp);
+#endif
+
+ } else {
+ // compare string
+ char str[SCRIPT_MAXSSIZE];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ if (lastop==OPER_EQUEQU || lastop==OPER_NOTEQU) {
+ uint8_t res=0;
+ res=strcmp(cmpstr,str);
+ if (lastop==OPER_EQUEQU) res=!res;
+ if (!and_or) {
+ if_result[s_ifstck]=res;
+ } else if (and_or==1) {
+ if_result[s_ifstck]|=res;
+ } else {
+ if_result[s_ifstck]&=res;
+ }
+ }
+ }
+ SCRIPT_SKIP_SPACES
+ if (*lp=='{' && if_state[ifstck]==1) {
+ lp+=1; // then
+ if_state[ifstck]=2;
+ }
+ goto next_line;
+ } else {
+ if (numeric) {
+ char *slp=lp;
+ glob_script_mem.glob_error=0;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,jo);
+ if (glob_script_mem.glob_error==1) {
+ // mismatch was string, not number
+ // get the string and convert to number
+ lp=isvar(slp,&vtype,&ind,0,cmpstr,jo);
+ fvar=CharToDouble(cmpstr);
+ }
+ switch (lastop) {
+ case OPER_EQU:
+ if (glob_script_mem.var_not_found) {
+ if (!js) toLog("var not found\n");
+ goto next_line;
+ }
+ *dfvar=fvar;
+ break;
+ case OPER_PLSEQU:
+ *dfvar+=fvar;
+ break;
+ case OPER_MINEQU:
+ *dfvar-=fvar;
+ break;
+ case OPER_MULEQU:
+ *dfvar*=fvar;
+ break;
+ case OPER_DIVEQU:
+ *dfvar/=fvar;
+ break;
+ case OPER_PERCEQU:
+ *dfvar=fmod(*dfvar,fvar);
+ break;
+ case OPER_ANDEQU:
+ *dfvar=(uint32_t)*dfvar&(uint32_t)fvar;
+ break;
+ case OPER_OREQU:
+ *dfvar=(uint32_t)*dfvar|(uint32_t)fvar;
+ break;
+ case OPER_XOREQU:
+ *dfvar=(uint32_t)*dfvar^(uint32_t)fvar;
+ break;
+ default:
+ // error
+ break;
+ }
+ // var was changed
+ glob_script_mem.type[globvindex].bits.changed=1;
+ if (glob_script_mem.type[globvindex].bits.is_filter) {
+ Set_MFilter(glob_script_mem.type[globvindex].index,*dfvar);
+ }
+
+ if (sysv_type) {
+ switch (sysv_type) {
+ case SCRIPT_LOGLEVEL:
+ glob_script_mem.script_loglevel=*dfvar;
+ break;
+ case SCRIPT_TELEPERIOD:
+ if (*dfvar<10) *dfvar=10;
+ if (*dfvar>300) *dfvar=300;
+ Settings.tele_period=*dfvar;
+ break;
+ }
+ sysv_type=0;
+ }
+
+ } else {
+ // string result
+ char str[SCRIPT_MAXSSIZE];
+ char *slp=lp;
+ lp=GetStringResult(lp,OPER_EQU,str,jo);
+ if (!js && glob_script_mem.var_not_found) {
+ // mismatch
+ lp=GetNumericResult(slp,OPER_EQU,&fvar,0);
+ dtostrfd(fvar,6,str);
+ glob_script_mem.var_not_found=0;
+ }
+
+ if (!glob_script_mem.var_not_found) {
+ // var was changed
+ glob_script_mem.type[globvindex].bits.changed=1;
+ if (lastop==OPER_EQU) {
+ strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize);
+ } else if (lastop==OPER_PLSEQU) {
+ strlcat(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize);
+ }
+ }
+ }
+ SCRIPT_SKIP_SPACES
+ if (*lp=='{' && if_state[ifstck]==3) {
+ lp+=1; // else
+ //if_state[ifstck]=3;
+ }
+ goto next_line;
+ }
+ } else {
+ // decode line
+ if (*lp=='>' && tlen==1) {
+ // called from cmdline
+ lp++;
+ section=1;
+ goto startline;
+ }
+ if (!strncmp(lp,type,tlen)) {
+ // found section
+ section=1;
+ // check for subroutine
+ if (*type=='#') {
+ // check for parameter
+ type+=tlen;
+ if (*type=='(') {
+ float fparam;
+ numeric=1;
+ glob_script_mem.glob_error=0;
+ GetNumericResult((char*)type,OPER_EQU,&fparam,0);
+ if (glob_script_mem.glob_error==1) {
+ // was string, not number
+ numeric=0;
+ // get the string
+ GetStringResult((char*)type+1,OPER_EQU,cmpstr,0);
+ }
+ lp+=tlen;
+ if (*lp=='(') {
+ // fetch destination
+ lp++;
+ lp=isvar(lp,&vtype,&ind,0,0,0);
+ if (vtype!=VAR_NV) {
+ // found variable as result
+ uint8_t index=glob_script_mem.type[ind.index].index;
+ if ((vtype&STYPE)==0) {
+ // numeric result
+ dfvar=&glob_script_mem.fvars[index];
+ if (numeric) {
+ *dfvar=fparam;
+ } else {
+ // mismatch
+ *dfvar=CharToDouble(cmpstr);
+ }
+ } else {
+ // string result
+ sindex=index;
+ if (!numeric) {
+ strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),cmpstr,glob_script_mem.max_ssize);
+ } else {
+ // mismatch
+ dtostrfd(fparam,6,glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // next line
+ next_line:
+ if (*lp==SCRIPT_EOL) {
+ lp++;
+ } else {
+ lp = strchr(lp, SCRIPT_EOL);
+ if (!lp) break;
+ lp++;
+ }
+ }
+}
+
+uint8_t script_xsns_index = 0;
+
+
+void ScripterEvery100ms(void)
+{
+ if (Settings.rule_enabled && (uptime > 4)) {
+ mqtt_data[0] = '\0';
+ uint16_t script_tele_period_save = tele_period;
+ tele_period = 2;
+ XsnsNextCall(FUNC_JSON_APPEND, script_xsns_index);
+ tele_period = script_tele_period_save;
+ if (strlen(mqtt_data)) {
+ mqtt_data[0] = '{';
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data);
+ Run_Scripter(">T",2, mqtt_data);
+ }
+ }
+}
+
+//mems[MAX_RULE_MEMS] is 50 bytes in 6.5
+// can hold 11 floats or floats + strings
+// should report overflow later
+void Scripter_save_pvars(void) {
+ uint32_t lptr=(uint32_t)Settings.mems[0];
+ int16_t mlen=0;
+ lptr&=0xfffffffc;
+ float *fp=(float*)lptr;
+ fp++;
+ mlen+=sizeof(float);
+ struct T_INDEX *vtp=glob_script_mem.type;
+ for (uint8_t count=0; countMAX_RULE_MEMS*10) {
+ vtp[count].bits.is_permanent=0;
+ return;
+ }
+ *fp++=glob_script_mem.fvars[index];
+ }
+ }
+ char *cp=(char*)fp;
+ for (uint8_t count=0; countMAX_RULE_MEMS*10) {
+ vtp[count].bits.is_permanent=0;
+ return;
+ }
+ strcpy(cp,sp);
+ cp+=slen+1;
+ }
+ }
+}
+
+// works only with webserver
+#ifdef USE_WEBSERVER
+
+#define WEB_HANDLE_SCRIPT "s10"
+#define D_CONFIGURE_SCRIPT "Edit script"
+#define D_RULEVARS "edit script"
+
+const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT;
+
+const char HTTP_BTN_MENU_RULES[] PROGMEM =
+ "
";
+
+
+const char HTTP_FORM_SCRIPT[] PROGMEM =
+ "