**Script Language for Tasmota**  

As an alternative to rules. (about 17k 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  
hexadecimal numbers are supported with prefix 0x

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) optional another filter lenght (1..127) can be given after the definition.  
filter vars can be accessed also in indexed mode vname[x] (index = 1...N, index 0 returns current array index pointer)  
by this filter vars can be used as arrays  

>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  and script save

>**\>T**  
executed on teleperiod time (**SENSOR** and **STATE**), get tele vars only in this section  
remark: json variable names (like all others) may not contain math operators like - , you should set setoption64 1 to replace - with underscore

>**\>F**  
executed every 100 ms  

>**\>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)  
**pc[x]** = tasmota pulse counter value  (x = 1-4)  
**tbut[x]** = touch screen button 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) to a hex nibble string  
**st(svar c n)** = stringtoken gets the n th substring of svar separated by c
**sl(svar)** = gets the length of a string
**sb(svar p n)** = gets a substring from svar at position p (if p<0 counts from end) and length n     
**s(x)** = explicit conversion from number x to 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 **%%**  
**->** is equivalent but doesnt send mqtt or any weblog (silent execute, usefull to reduce traffic)

**special** cmds:

>**print** or **=\>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 be enclosed in brackets  
and on the same line conditions may be bracketed  e.g. if ((a==b) and ((c==d) or (c==e)) and (s!="x"))  


>**break** exits a section or terminates a for next loop  
**dpx** 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,input with pullup=2)  
**ws2812(array)** copies an array (defined with m:name) to the WS2812 LED chain  the array should be defined as long as the number of pixels. the color is coded as 24 bit RGB    
**hsvrgb(h s v)** converts hue (0-360), saturation (0-100) and value (0-100) to RGB color  

>**#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
**=(svar)** executes a script in a string variable (dynamic or self modifying code)  

>**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  (numeric or string)

**sd card support**  
enable by CARD_CS = gpio pin of card chip select (+ 10k flash)  
\#define USE_SCRIPT_FATFS CARD_CS   
sd card uses standard hardware spi gpios: mosi,miso,sclk  
max 4 files open at a time  
allows for e.g. logging sensors to a tab delimited file and then download (see example below)  
the download of files may be executed in a kind of "multitasking" when bit 7 of loglvl is set (128+loglevel)  
without multitasking 150kb/s (all processes are stopped during download), with multitasking 50kb/s (other tasmota processes are running)  
script itself is also stored on sdcard with a default size of 4096 chars  


enable sd card directory support (+ 1,2k flash)  
\#define SDCARD_DIR  
shows a web sdcard directory (submeu of scripter) where you can
upload and download files to/from sd card  


>**fr=fo("fname" m)** open file fname, mode 0=read, 1=write (returns file reference (0-3) or -1 for error)  
**res=fw("text" fr)** writes text to (the end of) file fr, returns number of bytes written  
**res=fr(svar fr)** reads a string into svar, returns bytes read (string is read until delimiter \t \n \r or eof)  
**fc(fr)** close file  
**ff(fr)** flush file, writes cached data and updates directory     
**fd("fname")** delete file fname   
**flx(fname)** create download link for file (x=1 or 2) fname = file name of file to download   
**fsm** return 1 if filesystem is mounted, (valid sd card found)  

extended commands   (+0,9k flash)
\#define USE_SCRIPT_FATFS_EXT  
>**fmd("fname")** make directory fname  
>**frd("fname")** remove directory fname  
>**fx("fname")** check if file fname exists  
>**fe("fname")** execute script fname  (max 2048 bytes, script file must start with '>' char on the 1. line)  


**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

**subscribe,unsubscribe**
>if \#defined SUPPORT_MQTT_EVENT command subscribe and unsubscribe are supported. in contrast to rules no event is generated but the event name specifies a variable defined in D section and this variable is automatically set on transmission of the subscribed item  

**summary of optional defines**  
>\#define USE_SCRIPT_FATFS CS_PIN : enables SD card support (on spi bus) also enables 4k script buffer  
\#define USE_SCRIPT_FATFS_EXT : enables additional FS commands  
\#define SDCARD_DIR : enables support for WEBUI for SD card directory up and download  
\#define USE_24C256 : enables use of 24C256 i2c eeprom to expand script buffer (defaults to 4k)  
\#define SUPPORT_MQTT_EVENT : enables support for subscribe unsubscribe  
\#define USE_TOUCH_BUTTONS : enable virtual touch button support with touch displays  


***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=&quot;hello world&quot;  
string=&quot;xxx&quot;  
url=&quot;[192.168.178.86]&quot;  
hum=0  
temp=0  
timer=0  
dimmer=0  
sw=0  
rssi=0  
param=0  

col=&quot;&quot;  
ocol=&quot;&quot;  
chan1=0  
chan2=0  
chan3=0  

ahum=0  
atemp=0  
tcnt=0  
hour=0
state=1  
m:med5=0  
M:movav=0  
; define array with 10 entries  
m:array=0 10  

**\>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  

; arrays  
array[1]=4  
array[2]=5  
tmp=array[1]+array[2]  

; 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  
dp0  
=\>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:<sender@gmail.de>:<rec@gmail.de>:alarm] %string%  

**\>E**  
=\>print event executed!  

; get HSBColor 1. component  
tmp=st(HSBColor , 1)  

; check if switch changed state  
sw=sw[1]  
if chg[sw]>0  
then =\>power1 %sw%  
endif  

hello=&quot;event occured&quot;  

; 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 log sensor example**  
; define all vars here  
; reserve large strings  
**\>D** 48  
hum=0  
temp=0  
fr=0  
res=0  
; moving average for 60 seconds  
M:mhum=0 60  
M:mtemp=0 60  
str=""  

**\>B**  
; set sensor file download link   
fl1("slog.txt")  
; delete file in case we want to start fresh  
;fd("slog.txt")  


; list all files in root directory  
fr=fo("/" 0)  
for cnt 1 20 1  
res=fr(str fr)  
if res>0  
then  
=>print %cnt% : %str%  
else  
break  
endif  
next  
fc(fr)  


**\>T**  
; get sensor values  
temp=BME280#Temperature  
hum=BME280#Humidity  

**\>S**
; average sensor values every second  
mhum=hum  
mtemp=temp  

; write average to sensor log every minute  
if upsecs%60==0  
then  
; open file for write  
fr=fo("slog.txt" 1)  
; compose string for tab delimited file entry  
str=s(upsecs)+"\t"+s(mhum)+"\t"+s(mtemp)+"\n"  
; write string to log file  
res=fw(str fr)  
; close file  
fc(fr)  
endif  

**\>R**  



**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  
dp2  
=>%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  

dp0  
=>%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  
dp2  
=>%DT% [f1Ci3x40y260w30Ci1]  
=>%DT% [Ci7x120y220t]  
=>%DT% [Ci7x180y220T]  
=>%DT% [Ci7p8x120y240]%temp% %tunit%   
=>%DT% [Ci7x120y260]%press% %punit%  
=>%DT% [Ci7x120y280]%dist% mm  
dp0  
=>%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**
