From dad6d9f9978181a213b7eeb7af43b0cd32f96b92 Mon Sep 17 00:00:00 2001
From: gemu2015
Date: Sat, 15 Jun 2019 07:02:34 +0200
Subject: [PATCH] update scripter
bug fixes and enhancements
---
scripter.md | 50 +++-
sonoff/xdrv_10_scripter.ino | 491 ++++++++++++++++++++++++++++++------
2 files changed, 452 insertions(+), 89 deletions(-)
diff --git a/scripter.md b/scripter.md
index 40ad3451e..43c966b6b 100644
--- a/scripter.md
+++ b/scripter.md
@@ -167,7 +167,7 @@ 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)
+**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
@@ -189,22 +189,38 @@ specifies a for next loop, (loop count must not be less then 1)
specifies a switch case selector
**sd card support**
-enable by CARD_CS = gpio pin of card chip select
+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
-requires additional 10k flash
+
+
+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
@@ -506,10 +522,25 @@ M:mtemp=0 60
str=""
**\>B**
-; open file for write
-fr=fo("slog.txt" 1)
; 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
@@ -524,15 +555,18 @@ mtemp=temp
; write average to sensor log every minute
if upsecs%60==0
then
-; compose string for tab delimited file entry
+; 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**
-; close file
-fc(fr)
+
**a real example**
diff --git a/sonoff/xdrv_10_scripter.ino b/sonoff/xdrv_10_scripter.ino
index d4734ba68..f9ae2762c 100644
--- a/sonoff/xdrv_10_scripter.ino
+++ b/sonoff/xdrv_10_scripter.ino
@@ -36,12 +36,9 @@ no math hierarchy (costs ram and execution time, better group with brackets, an
keywords if then else endif, or, and are better readable for beginners (others may use {})
changelog after merging to Tasmota
-1 show remaining chars in webui,
-2 now can expand script space to 2048 chars by setting MAX_RULE_SETS to 4
-3 at24256 eeprom support #ifdef defaults to 4095 bytes script size (reduces heap by this amount)
-4 some housekeeping
-5 sd card support #ifdef allows eg for sensor logging
-6 download link for sdcard files
+1. draw color picture from sd card
+2. upload files to sc card
+
\*********************************************************************************************/
@@ -96,6 +93,21 @@ struct M_FILT {
float rbuff[1];
};
+
+typedef union {
+ uint8_t data;
+ struct {
+ uint8_t nutu8 : 1;
+ uint8_t nutu7 : 1;
+ uint8_t nutu6 : 1;
+ uint8_t nutu5 : 1;
+ uint8_t nutu4 : 1;
+ uint8_t nutu3 : 1;
+ uint8_t is_dir : 1;
+ uint8_t is_open : 1;
+ };
+} FILE_FLAGS;
+
#define SFS_MAX 4
// global memory
struct SCRIPT_MEM {
@@ -122,7 +134,7 @@ struct SCRIPT_MEM {
uint8_t flags;
#ifdef USE_SCRIPT_FATFS
File files[SFS_MAX];
- uint8_t file_flags[SFS_MAX];
+ FILE_FLAGS file_flags[SFS_MAX];
uint8_t script_sd_found;
char flink[2][14];
#endif
@@ -139,6 +151,7 @@ char *GetNumericResult(char *lp,uint8_t lastop,float *fp,JsonObject *jo);
char *GetStringResult(char *lp,uint8_t lastop,char *cp,JsonObject *jo);
char *ForceStringVar(char *lp,char *dstr);
void send_download(void);
+uint8_t reject(char *name);
void ScriptEverySecond(void) {
@@ -496,7 +509,7 @@ char *script;
}
}
for (uint8_t cnt=0;cnt=SFS_MAX) ind=SFS_MAX-1;
glob_script_mem.files[ind].close();
- glob_script_mem.file_flags[ind]=0;
+ glob_script_mem.file_flags[ind].is_open=0;
+ fvar=0;
+ lp++;
+ len=0;
+ goto exit;
+ }
+ if (!strncmp(vname,"ff(",3)) {
+ lp+=3;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
+ uint8_t ind=fvar;
+ if (ind>=SFS_MAX) ind=SFS_MAX-1;
+ glob_script_mem.files[ind].flush();
fvar=0;
lp++;
len=0;
@@ -923,7 +955,7 @@ chknext:
lp=GetNumericResult(lp,OPER_EQU,&fvar,0);
uint8_t ind=fvar;
if (ind>=SFS_MAX) ind=SFS_MAX-1;
- if (glob_script_mem.file_flags[ind]&1) {
+ if (glob_script_mem.file_flags[ind].is_open) {
fvar=glob_script_mem.files[ind].print(str);
} else {
fvar=0;
@@ -960,19 +992,37 @@ chknext:
uint8_t index=0;
char str[glob_script_mem.max_ssize+1];
char *cp=str;
- if (glob_script_mem.file_flags[find]&1) {
- while (glob_script_mem.files[find].available()) {
- uint8_t buf[1];
- glob_script_mem.files[find].read(buf,1);
- if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') {
- break;
- } else {
- *cp++=buf[0];
- index++;
- if (index>=glob_script_mem.max_ssize-1) break;
+ if (glob_script_mem.file_flags[find].is_open) {
+ if (glob_script_mem.file_flags[find].is_dir) {
+ while (true) {
+ File entry=glob_script_mem.files[find].openNextFile();
+ if (entry) {
+ if (!reject((char*)entry.name())) {
+ strcpy(str,entry.name());
+ entry.close();
+ break;
+ }
+ } else {
+ *cp=0;
+ break;
+ }
+ entry.close();
}
+ index=strlen(str);
+ } else {
+ while (glob_script_mem.files[find].available()) {
+ uint8_t buf[1];
+ glob_script_mem.files[find].read(buf,1);
+ if (buf[0]=='\t' || buf[0]==',' || buf[0]=='\n' || buf[0]=='\r') {
+ break;
+ } else {
+ *cp++=buf[0];
+ index++;
+ if (index>=glob_script_mem.max_ssize-1) break;
+ }
+ }
+ *cp=0;
}
- *cp=0;
} else {
strcpy(str,"file error");
}
@@ -991,6 +1041,59 @@ chknext:
len=0;
goto exit;
}
+#ifdef USE_SCRIPT_FATFS_EXT
+ if (!strncmp(vname,"fe(",3)) {
+ lp+=3;
+ char str[glob_script_mem.max_ssize+1];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ // execute script
+ File ef=SD.open(str);
+ if (ef) {
+ uint16_t fsiz=ef.size();
+ if (fsiz<2048) {
+ char *script=(char*)calloc(fsiz+16,1);
+ if (script) {
+ ef.read((uint8_t*)script,fsiz);
+ execute_script(script);
+ free(script);
+ fvar=1;
+ }
+ }
+ ef.close();
+ }
+ lp++;
+ len=0;
+ goto exit;
+ }
+ if (!strncmp(vname,"fmd(",4)) {
+ lp+=4;
+ char str[glob_script_mem.max_ssize+1];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ fvar=SD.mkdir(str);
+ lp++;
+ len=0;
+ goto exit;
+ }
+ if (!strncmp(vname,"frd(",4)) {
+ lp+=4;
+ char str[glob_script_mem.max_ssize+1];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ fvar=SD.rmdir(str);
+ lp++;
+ len=0;
+ goto exit;
+ }
+ if (!strncmp(vname,"fx(",3)) {
+ lp+=3;
+ char str[glob_script_mem.max_ssize+1];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ if (SD.exists(str)) fvar=1;
+ else fvar=0;
+ lp++;
+ len=0;
+ goto exit;
+ }
+#endif
if (!strncmp(vname,"fl1(",4) || !strncmp(vname,"fl2(",4) ) {
uint8_t lknum=*(lp+2)&3;
lp+=4;
@@ -1746,6 +1849,7 @@ void toSLog(const char *str) {
+//#define IFTHEN_DEBUG
#define IF_NEST 8
// execute section of scripter
@@ -1800,7 +1904,8 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
}
glob_script_mem.var_not_found=0;
-#if SCRIPT_DEBUG>0
+//#if SCRIPT_DEBUG>0
+#ifdef IFTHEN_DEBUG
char tbuff[128];
sprintf(tbuff,"stack=%d,state=%d,cmpres=%d line: ",ifstck,if_state[ifstck],if_result[ifstck]);
toLogEOL(tbuff,lp);
@@ -1825,6 +1930,8 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
if (ifstck>0) {
ifstck--;
}
+ if (if_state[ifstck]==3 && if_result[ifstck]) goto next_line;
+ if (if_state[ifstck]==2 && !if_result[ifstck]) goto next_line;
s_ifstck=ifstck; // >>>>>
goto next_line;
} else if (!strncmp(lp,"or",2) && if_state[ifstck]==1) {
@@ -1866,6 +1973,8 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
if (ifstck>0) {
ifstck--;
}
+ if (if_state[ifstck]==3 && if_result[ifstck]) goto next_line;
+ if (if_state[ifstck]==2 && !if_result[ifstck]) goto next_line;
s_ifstck=ifstck; // >>>>>
}
}
@@ -1926,16 +2035,20 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
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;
+#ifdef IFTHEN_DEBUG
+ sprintf(tbuff,"stack=%d,state=%d,cmpres=%d execute line: ",ifstck,if_state[ifstck],if_result[ifstck]);
+ toLogEOL(tbuff,lp);
+#endif
s_ifstck=ifstck;
if (!strncmp(lp,"break",5)) {
@@ -1946,8 +2059,8 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
section=0;
}
break;
- } else if (!strncmp(lp,"dprec",5)) {
- lp+=5;
+ } else if (!strncmp(lp,"dp",2) && isdigit(*(lp+2))) {
+ lp+=2;
// number precision
glob_script_mem.script_dprec=atoi(lp);
goto next_line;
@@ -1987,6 +2100,7 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
else if (!strncmp(lp,"=>",2)) {
// execute cmd
lp+=2;
+ char *slp=lp;
SCRIPT_SKIP_SPACES
#define SCRIPT_CMDMEM 512
char *cmdmem=(char*)malloc(SCRIPT_CMDMEM);
@@ -2019,6 +2133,7 @@ int16_t Run_Scripter(const char *type, uint8_t tlen, char *js) {
}
if (cmdmem) free(cmdmem);
}
+ lp=slp;
goto next_line;
} else if (!strncmp(lp,"=#",2)) {
// subroutine
@@ -2374,6 +2489,9 @@ void Scripter_save_pvars(void) {
#define WEB_HANDLE_SCRIPT "s10"
#define D_CONFIGURE_SCRIPT "Edit script"
#define D_SCRIPT "edit script"
+#define D_SDCARD_UPLOAD "file upload"
+#define D_SDCARD_DIR "sd card directory"
+#define D_UPL_DONE "Done"
const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT;
@@ -2387,8 +2505,8 @@ const char HTTP_FORM_SCRIPT[] PROGMEM =
const char HTTP_FORM_SCRIPT1[] PROGMEM =
"
"
- "script enable
"
- "
"),"/upl",D_UPL_DONE);
+ //WSContentSpaceButton(BUTTON_MAIN);
+ WSContentStop();
+}
+
+void script_upload(void) {
+
+ //AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: file upload"));
+
+ HTTPUpload& upload = WebServer->upload();
+ if (upload.status == UPLOAD_FILE_START) {
+ char npath[48];
+ sprintf(npath,"%s/%s",path,upload.filename.c_str());
+ SD.remove(npath);
+ upload_file=SD.open(npath,FILE_WRITE);
+ if (!upload_file) upload_error=1;
+ } else if(upload.status == UPLOAD_FILE_WRITE) {
+ if (upload_file) upload_file.write(upload.buf,upload.currentSize);
+ } else if(upload.status == UPLOAD_FILE_END) {
+ if (upload_file) upload_file.close();
+ if (upload_error) {
+ AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: upload error"));
+ }
+ } else {
+ upload_error=1;
+ WebServer->send(500, "text/plain", "500: couldn't create file");
+ }
+}
+
+uint8_t DownloadFile(char *file) {
+ File download_file;
+ WiFiClient download_Client;
+
+ if (!SD.exists(file)) {
+ toLog("file not found");
+ return 0;
+ }
+
+ download_file=SD.open(file,FILE_READ);
+ if (!download_file) {
+ toLog("could not open file");
+ return 0;
+ }
+
+ if (download_file.isDirectory()) {
+ download_file.close();
+ return 1;
+ }
+
+ uint32_t flen=download_file.size();
+
+ download_Client = WebServer->client();
+ WebServer->setContentLength(flen);
+
+ char attachment[100];
+ char *cp;
+ for (uint8_t cnt=strlen(file); cnt>=0; cnt--) {
+ if (file[cnt]=='/') {
+ cp=&file[cnt+1];
+ break;
+ }
+ }
+ snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=%s"),cp);
+ WebServer->sendHeader(F("Content-Disposition"), attachment);
+ WSSend(200, CT_STREAM, "");
+
+ uint8_t buff[512];
+ uint16_t bread;
+
+ // transfer is about 150kb/s
+ uint8_t cnt=0;
+ while (download_file.available()) {
+ bread=download_file.read(buff,sizeof(buff));
+ uint16_t bw=download_Client.write((const char*)buff,bread);
+ if (!bw) break;
+ cnt++;
+ if (cnt>7) {
+ cnt=0;
+ if (glob_script_mem.script_loglevel&0x80) {
+ // this indeed multitasks, but is slower 50 kB/s
+ loop();
+ }
+ }
+ }
+ download_file.close();
+ download_Client.stop();
+ return 0;
+}
+
#endif
void HandleScriptConfiguration(void)
@@ -2477,10 +2787,13 @@ void HandleScriptConfiguration(void)
#ifdef USE_SCRIPT_FATFS
if (WebServer->hasArg("d1")) {
- script_download(1);
+ DownloadFile(glob_script_mem.flink[0]);
}
if (WebServer->hasArg("d2")) {
- script_download(2);
+ DownloadFile(glob_script_mem.flink[1]);
+ }
+ if (WebServer->hasArg("upl")) {
+ Script_FileUploadConfiguration();
}
#endif
@@ -2496,8 +2809,11 @@ void HandleScriptConfiguration(void)
WSContentSend_P(HTTP_FORM_SCRIPT1b);
#ifdef USE_SCRIPT_FATFS
- if (glob_script_mem.flink[0][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,1,glob_script_mem.flink[0]);
- if (glob_script_mem.flink[1][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,2,glob_script_mem.flink[1]);
+ if (glob_script_mem.script_sd_found) {
+ WSContentSend_P(HTTP_FORM_SCRIPT1d);
+ if (glob_script_mem.flink[0][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,1,glob_script_mem.flink[0]);
+ if (glob_script_mem.flink[1][0]) WSContentSend_P(HTTP_FORM_SCRIPT1c,2,glob_script_mem.flink[1]);
+ }
#endif
WSContentSend_P(HTTP_FORM_END);
@@ -2516,8 +2832,6 @@ void strrepl_inplace(char *str, const char *a, const char *b) {
}
}
-
-
void ScriptSaveSettings(void) {
if (WebServer->hasArg("c1")) {
@@ -2610,7 +2924,7 @@ bool ScriptCommand(void) {
for (uint8_t count=0; counton("/" WEB_HANDLE_SCRIPT, HandleScriptConfiguration);
+#ifdef USE_SCRIPT_FATFS
+ WebServer->on("/u3", HTTP_POST,[]() { WebServer->sendHeader("Location","/u3");WebServer->send(303);},script_upload);
+ WebServer->on("/u3", HTTP_GET,ScriptFileUploadSuccess);
+ WebServer->on("/upl", HTTP_GET,Script_FileUploadConfiguration);
+#endif
break;
#endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART: