**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) 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 >**\>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) to a hex nibble string **st(svar c n)** = stringtoken gets the n th substring of svar separated by c **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 ; 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 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! ; 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="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**