Tasmota/tasmota/xdrv_10_scripter.ino

7487 lines
219 KiB
C++
Executable File

/*
xdrv_10_scripter.ino - script support for Tasmota
Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_SCRIPT
#ifndef USE_RULES
/*********************************************************************************************\
for documentation see up to date docs in file SCRIPTER.md
uses about 17 k of flash
to do
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 {})
\*********************************************************************************************/
#define XDRV_10 10
#define XI2C_37 37 // See I2CDEVICES.md
#define SCRIPT_DEBUG 0
#define FORMAT_SPIFFS_IF_FAILED true
#ifndef MAXVARS
#define MAXVARS 50
#endif
#ifndef MAXSVARS
#define MAXSVARS 5
#endif
#define MAXNVARS MAXVARS-MAXSVARS
#ifndef MAXFILT
#define MAXFILT 5
#endif
#define SCRIPT_SVARSIZE 20
#define SCRIPT_MAXSSIZE 48
#define SCRIPT_EOL '\n'
#define SCRIPT_FLOAT_PRECISION 2
#define PMEM_SIZE sizeof(Settings.script_pram)
#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float)
#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS
#define MAX_SARRAY_NUM 32
uint32_t EncodeLightId(uint8_t relay_id);
uint32_t DecodeLightId(uint32_t hue_id);
#ifdef USE_UNISHOX_COMPRESSION
#define USE_SCRIPT_COMPRESSION
#endif
// solve conficting defines
// highest priority
#ifdef USE_SCRIPT_FATFS
#undef LITTLEFS_SCRIPT_SIZE
#undef EEP_SCRIPT_SIZE
#undef USE_SCRIPT_COMPRESSION
#if USE_SCRIPT_FATFS==-1
#ifdef ESP32
#pragma message "script fat file option -1 used"
#else
#pragma message "script fat file option -1 used"
#endif
#else
#pragma message "script fat file SDC option used"
#endif
#endif // USE_SCRIPT_FATFS
// lfs on esp8266 spiffs on esp32
#ifdef LITTLEFS_SCRIPT_SIZE
#undef EEP_SCRIPT_SIZE
#undef USE_SCRIPT_COMPRESSION
#pragma message "script little file system option used"
#endif // LITTLEFS_SCRIPT_SIZE
// eeprom script
#ifdef EEP_SCRIPT_SIZE
#undef USE_SCRIPT_COMPRESSION
#ifdef USE_24C256
#pragma message "script 24c256 file option used"
#else
//#warning "EEP_SCRIPT_SIZE also needs USE_24C256"
#pragma message "internal eeprom script buffer used"
//#define USE_24C256
#endif
#endif // EEP_SCRIPT_SIZE
// compression last option before default
#ifdef USE_SCRIPT_COMPRESSION
#pragma message "script compression option used"
#endif // USE_UNISHOX_COMPRESSION
#ifdef USE_SCRIPT_COMPRESSION
#include <unishox.h>
#define SCRIPT_COMPRESS compressor.unishox_compress
#define SCRIPT_DECOMPRESS compressor.unishox_decompress
#ifndef UNISHOXRSIZE
#define UNISHOXRSIZE 2560
#endif
#endif // USE_SCRIPT_COMPRESSION
#ifdef USE_SCRIPT_TIMER
#include <Ticker.h>
Ticker Script_ticker1;
Ticker Script_ticker2;
Ticker Script_ticker3;
Ticker Script_ticker4;
void Script_ticker1_end(void) {
Script_ticker1.detach();
Run_Scripter(">ti1", 4, 0);
}
void Script_ticker2_end(void) {
Script_ticker2.detach();
Run_Scripter(">ti2", 4, 0);
}
void Script_ticker3_end(void) {
Script_ticker3.detach();
Run_Scripter(">ti3", 4, 0);
}
void Script_ticker4_end(void) {
Script_ticker4.detach();
Run_Scripter(">ti4", 4, 0);
}
#endif
#if defined(LITTLEFS_SCRIPT_SIZE) || (USE_SCRIPT_FATFS==-1)
#ifdef ESP32
#include "FS.h"
#ifdef LITTLEFS_SCRIPT_SIZE
#include "SPIFFS.h"
#else
#include "FFat.h"
#endif
#else
#include <LittleFS.h>
#endif
FS *fsp;
#endif // LITTLEFS_SCRIPT_SIZE
#ifdef LITTLEFS_SCRIPT_SIZE
void SaveFile(const char *name, const uint8_t *buf, uint32_t len) {
File file = fsp->open(name, "w");
if (!file) return;
file.write(buf, len);
file.close();
}
uint8_t fs_mounted=0;
void LoadFile(const char *name, uint8_t *buf, uint32_t len) {
if (!fs_mounted) {
#ifdef ESP32
if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
#else
if (!fsp->begin()) {
#endif
//Serial.println("SPIFFS Mount Failed");
return;
}
fs_mounted=1;
}
File file = fsp->open(name, "r");
if (!file) return;
file.read(buf, len);
file.close();
}
#endif // LITTLEFS_SCRIPT_SIZE
// offsets epoch readings by 1.1.2019 00:00:00 to fit into float with second resolution
#define EPOCH_OFFSET 1546300800
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,SCRIPT_EVENT_HANDLED};
#ifdef USE_SCRIPT_FATFS
#if USE_SCRIPT_FATFS>=0
#include <SPI.h>
#include <SD.h>
#ifdef ESP32
FS *fsp;
#else
SDClass *fsp;
#endif
#endif //USE_SCRIPT_FATFS
#ifndef ESP32
// esp8266
#if USE_SCRIPT_FATFS>=0
// old fs
#undef FILE_WRITE
#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT)
#define FILE_APPEND (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND)
#else
// new fs
#undef FILE_WRITE
#define FILE_WRITE "w"
#undef FILE_READ
#define FILE_READ "r"
#undef FILE_APPEND
#define FILE_APPEND "a"
#endif
#endif // USE_SCRIPT_FATFS>=0
#ifndef FAT_SCRIPT_SIZE
#define FAT_SCRIPT_SIZE 4096
#endif
#ifdef ESP32
#undef FAT_SCRIPT_NAME
#define FAT_SCRIPT_NAME "/script.txt"
#else
#undef FAT_SCRIPT_NAME
#define FAT_SCRIPT_NAME "script.txt"
#endif
//#if USE_STANDARD_SPI_LIBRARY==0
//#warning ("FATFS standard spi should be used");
//#endif
#endif // USE_SCRIPT_FATFS
#ifdef SUPPORT_MQTT_EVENT
#include <LinkedList.h> // Import LinkedList library
typedef struct {
String Event;
String Topic;
String Key;
} MQTT_Subscription;
LinkedList<MQTT_Subscription> subscriptions;
#endif //SUPPORT_MQTT_EVENT
#ifdef USE_DISPLAY
#ifdef USE_TOUCH_BUTTONS
#include <renderer.h>
extern VButton *buttons[MAXBUTTONS];
#endif
#endif
typedef union {
#ifdef USE_SCRIPT_GLOBVARS
uint16_t data;
#else
uint8_t data;
#endif
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;
#ifdef USE_SCRIPT_GLOBVARS
uint8_t global : 1;
#endif
};
} SCRIPT_TYPE;
struct T_INDEX {
uint8_t index;
SCRIPT_TYPE bits;
};
struct M_FILT {
#ifdef LARGE_ARRAYS
uint16_t numvals;
uint16_t index;
#else
uint8_t numvals;
uint8_t index;
#endif // LARGE_ARRAYS
float maccu;
float rbuff[1];
};
#ifdef LARGE_ARRAYS
#undef AND_FILT_MASK
#undef OR_FILT_MASK
#define AND_FILT_MASK 0x7fff
#define OR_FILT_MASK 0x8000
#undef MAX_ARRAY_SIZE
#define MAX_ARRAY_SIZE 1000
#else
#undef AND_FILT_MASK
#undef OR_FILT_MASK
#define AND_FILT_MASK 0x7f
#define OR_FILT_MASK 0x80
#undef MAX_ARRAY_SIZE
#define MAX_ARRAY_SIZE 127
#endif
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;
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 udp_connected : 1;
uint8_t udp_used : 1;
};
} UDP_FLAGS;
#define NUM_RES 0xfe
#define STR_RES 0xfd
#define VAR_NV 0xff
#define NTYPE 0
#define STYPE 0x80
#ifndef FLT_MAX
#define FLT_MAX 99999999
#endif
#define SFS_MAX 4
// 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
#ifdef SCRIPT_LARGE_VNBUFF
uint16_t *vnp_offset;
#else
uint8_t *vnp_offset;
#endif
char *glob_snp; // string vars pointer
char *scriptptr;
char *section_ptr;
char *scriptptr_bu;
char *script_ram;
uint16_t script_size;
uint8_t *script_pram;
uint16_t script_pram_size;
uint8_t numvars;
void *script_mem;
uint16_t script_mem_size;
uint8_t script_dprec;
uint8_t script_lzero;
uint8_t var_not_found;
uint8_t glob_error;
uint8_t max_ssize;
uint8_t script_loglevel;
uint8_t flags;
uint8_t si_num[3];
uint8_t siro_num[3];
uint8_t sind_num;
char *last_index_string[3];
#ifdef USE_SCRIPT_FATFS
File files[SFS_MAX];
FILE_FLAGS file_flags[SFS_MAX];
uint8_t script_sd_found;
char flink[2][14];
#endif //USE_SCRIPT_FATFS
#ifdef USE_SCRIPT_GLOBVARS
UDP_FLAGS udp_flags;
#endif
} glob_script_mem;
bool event_handeled = false;
#ifdef USE_SCRIPT_GLOBVARS
IPAddress last_udp_ip;
WiFiUDP Script_PortUdp;
#ifndef USE_DEVICE_GROUPS
char * IPAddressToString(const IPAddress& ip_address) {
static char ipbuffer[16];
sprintf_P(ipbuffer, PSTR("%u.%u.%u.%u"), ip_address[0], ip_address[1], ip_address[2], ip_address[3]);
return ipbuffer;
}
#endif //USE_DEVICE_GROUPS
#endif //USE_SCRIPT_GLOBVARS
int16_t last_findex;
int16_t last_sindex;
uint8_t tasm_cmd_activ=0;
uint8_t fast_script=0;
uint8_t glob_script=0;
uint32_t script_lastmillis;
void flt2char(float num, char *nbuff) {
dtostrfd(num, glob_script_mem.script_dprec, nbuff);
}
// convert float to char with leading zeros
void f2char(float num, uint32_t dprec, uint32_t lzeros, char *nbuff) {
dtostrfd(num, dprec, nbuff);
if (lzeros>1) {
// check leading zeros
uint32_t nd = num;
nd/=10;
nd+=1;
if (lzeros>nd) {
// insert zeros
char cpbuf[24];
char *cp = cpbuf;
for (uint32_t cnt = 0; cnt < lzeros - nd; cnt++) {
*cp++='0';
}
*cp=0;
strcat(cpbuf,nbuff);
strcpy(nbuff,cpbuf);
}
}
}
#ifdef USE_BUTTON_EVENT
int8_t script_button[MAX_KEYS];
#endif //USE_BUTTON_EVENT
char *GetNumericArgument(char *lp,uint8_t lastop,float *fp, JsonParserObject *jo);
char *GetStringArgument(char *lp,uint8_t lastop,char *cp, JsonParserObject *jo);
char *ForceStringVar(char *lp,char *dstr);
void send_download(void);
uint8_t reject(char *name);
void ScriptEverySecond(void) {
if (bitRead(Settings.rule_enabled, 0)) {
struct T_INDEX *vtp = glob_script_mem.type;
float delta = (millis() - script_lastmillis) / 1000.0;
script_lastmillis = millis();
for (uint8_t count=0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_timer) {
// decrements timers
float *fp = &glob_script_mem.fvars[vtp[count].index];
if (*fp>0) {
// 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) && mqtt_data[0]) Run_Scripter(">T", 2, mqtt_data);
}
// EEPROM MACROS
// i2c eeprom
#define EEP_WRITE(A,B,C) eeprom_writeBytes(A, B, (uint8_t*)C);
#define EEP_READ(A,B,C) eeprom_readBytes(A, B, (uint8_t*)C);
#define SCRIPT_SKIP_SPACES while (*lp==' ' || *lp=='\t') lp++;
#define SCRIPT_SKIP_EOL while (*lp==SCRIPT_EOL) lp++;
float *Get_MFAddr(uint8_t index, uint16_t *len, uint16_t *ipos);
// allocates all variables and presets them
int16_t Init_Scripter(void) {
char *script;
script = glob_script_mem.script_ram;
// scan lines for >DEF
uint16_t lines = 0;
uint16_t nvars = 0;
uint16_t svars = 0;
uint16_t 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;
uint8_t numflt = 0;
uint16_t 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=='>' || !*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 (numperm<SCRIPT_MAXPERM) {
vtypes[vars].bits.is_permanent = 1;
numperm++;
}
} else {
vtypes[vars].bits.is_permanent = 0;
}
if (*lp=='t' && *(lp+1)==':') {
lp += 2;
vtypes[vars].bits.is_timer = 1;
} else {
vtypes[vars].bits.is_timer = 0;
}
if (*lp=='i' && *(lp+1)==':') {
lp += 2;
vtypes[vars].bits.is_autoinc = 1;
} else {
vtypes[vars].bits.is_autoinc = 0;
}
#ifdef USE_SCRIPT_GLOBVARS
if (*lp=='g' && *(lp+1)==':') {
lp += 2;
vtypes[vars].bits.global = 1;
glob_script_mem.udp_flags.udp_used = 1;
} else {
vtypes[vars].bits.global = 0;
}
#endif //USE_SCRIPT_GLOBVARS
if ((*lp=='m' || *lp=='M') && *(lp+1)==':') {
uint8_t flg = *lp;
lp += 2;
if (*lp=='p' && *(lp+1)==':') {
vtypes[vars].bits.is_permanent = 1;
lp += 2;
}
if (flg=='M') mfilt[numflt].numvals = 8;
else mfilt[numflt].numvals = 5;
vtypes[vars].bits.is_filter = 1;
mfilt[numflt].index = 0;
if (flg=='M') {
mfilt[numflt].numvals |= OR_FILT_MASK;
}
vtypes[vars].index = numflt;
numflt++;
if (numflt>MAXFILT) {
return -6;
}
} else {
vtypes[vars].bits.is_filter = 0;
}
*vnp_p++ = vnames_p;
while (lp<op) {
*vnames_p++ = *lp++;
}
*vnames_p++ = 0;
// init variable
op++;
if (*op!='"') {
float fv;
if (*op=='0' && *(op+1)=='x') {
op += 2;
fv=strtol(op, &op, 16);
} else {
fv=CharToFloat(op);
}
fvalues[nvars] = fv;
vtypes[vars].bits.is_string = 0;
if (!vtypes[vars].bits.is_filter) vtypes[vars].index = nvars;
nvars++;
if (nvars>MAXNVARS) {
return -1;
}
if (vtypes[vars].bits.is_filter) {
while (isdigit(*op) || *op=='.' || *op=='-') {
op++;
}
while (*op==' ') op++;
if (isdigit(*op)) {
// lenght define follows
uint16_t flen = atoi(op);
if (flen>MAX_ARRAY_SIZE) {
// limit array size
flen = MAX_ARRAY_SIZE;
}
mfilt[numflt-1].numvals &= OR_FILT_MASK;
mfilt[numflt-1].numvals |= flen&AND_FILT_MASK;
}
}
} 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; count<numflt; count++) {
fsize += sizeof(struct M_FILT) + ((mfilt[count].numvals&AND_FILT_MASK) - 1)*sizeof(float);
}
// now copy vars to memory
uint32_t script_mem_size =
// number and number shadow vars
(sizeof(float)*nvars) +
(sizeof(float)*nvars) +
// var names
(vnames_p-vnames) +
// vars offsets
#ifdef SCRIPT_LARGE_VNBUFF
(sizeof(uint16_t)*vars) +
#else
(sizeof(uint8_t)*vars) +
#endif
// strings
(glob_script_mem.max_ssize*svars) +
// type array
(sizeof(struct T_INDEX)*vars) +
fsize;
script_mem_size += 16;
uint8_t *script_mem;
script_mem = (uint8_t*)calloc(script_mem_size, 1);
if (!script_mem) {
return -4;
}
glob_script_mem.script_mem = script_mem;
glob_script_mem.script_mem_size = script_mem_size;
// now copy all vars
// numbers
glob_script_mem.fvars = (float*)script_mem;
uint16_t size = sizeof(float)*nvars;
memcpy(script_mem, fvalues, size);
script_mem += size;
glob_script_mem.s_fvars = (float*)script_mem;
size = sizeof(float) * nvars;
memcpy(script_mem, fvalues, size);
script_mem += size;
glob_script_mem.mfilt = (struct M_FILT*)script_mem;
script_mem += fsize;
// memory types
glob_script_mem.type = (struct T_INDEX*)script_mem;
size = sizeof(struct T_INDEX)*vars;
memcpy(script_mem, vtypes, size);
script_mem += size;
// var name strings
char *namep = (char*)script_mem;
glob_script_mem.glob_vnp = (char*)script_mem;
size = vnames_p - vnames;
memcpy(script_mem, vnames, size);
script_mem += size;
#ifdef SCRIPT_LARGE_VNBUFF
glob_script_mem.vnp_offset = (uint16_t*)script_mem;
size = vars*sizeof(uint16_t);
#else
glob_script_mem.vnp_offset = (uint8_t*)script_mem;
size = vars*sizeof(uint8_t);
#endif
script_mem += size;
// strings
char *snamep = (char*)script_mem;
glob_script_mem.glob_snp = (char*)script_mem;
size = glob_script_mem.max_ssize * svars;
//memcpy(script_mem,strings,size);
script_mem += size;
// now must recalc memory offsets
uint16_t index = 0;
#ifdef SCRIPT_LARGE_VNBUFF
#ifndef MAXVNSIZ
#define MAXVNSIZ 4096
#endif
uint16_t *cp = glob_script_mem.vnp_offset;
#else
#undef MAXVNSIZ
#define MAXVNSIZ 255
uint8_t *cp = glob_script_mem.vnp_offset;
#endif
for (count = 0; count<vars; count++) {
*cp++ = index;
while (*namep) {
index++;
namep++;
}
namep++;
index++;
if (index > MAXVNSIZ) {
free(glob_script_mem.script_mem);
return -5;
}
}
// variables usage info
AddLog_P2(LOG_LEVEL_INFO, PSTR("Script: nv=%d, tv=%d, vns=%d, ram=%d"), nvars, svars, index, glob_script_mem.script_mem_size);
// copy string variables
char *cp1 = glob_script_mem.glob_snp;
char *sp = strings;
for (count = 0; count<svars; count++) {
strcpy(cp1,sp);
sp += strlen(sp) + 1;
cp1 += glob_script_mem.max_ssize;
}
// setup filter vars
uint8_t *mp = (uint8_t*)glob_script_mem.mfilt;
for (count = 0; count<numflt; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
mflp->numvals = mfilt[count].numvals;
mp += sizeof(struct M_FILT) + ((mfilt[count].numvals & AND_FILT_MASK) - 1) * sizeof(float);
}
glob_script_mem.numvars = vars;
glob_script_mem.script_dprec = SCRIPT_FLOAT_PRECISION;
glob_script_mem.script_lzero = 0;
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; count<glob_script_mem.numvars; count++) {
if (dvtp[count].bits.is_string) {
} else {
char string[32];
f2char(glob_script_mem.fvars[dvtp[count].index], glob_script_mem.script_dprec, glob_script_mem.script_lzero, string);
toLog(string);
}
}
#endif //SCRIPT_DEBUG
// now preset permanent vars
float *fp = (float*)glob_script_mem.script_pram;
struct T_INDEX *vtp = glob_script_mem.type;
for (uint8_t count = 0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index = vtp[count].index;
if (vtp[count].bits.is_filter) {
// preset array
uint16_t len = 0;
float *fa = Get_MFAddr(index, &len, 0);
while (len--) {
*fa++ = *fp++;
}
} else {
if (!isnan(*fp)) {
glob_script_mem.fvars[index] = *fp;
} else {
*fp = glob_script_mem.fvars[index];
}
fp++;
}
}
}
sp = (char*)fp;
for (uint8_t count = 0; count<glob_script_mem.numvars; count++) {
if (vtp[count].bits.is_permanent && vtp[count].bits.is_string) {
uint8_t index = vtp[count].index;
char *dp = glob_script_mem.glob_snp + (index * glob_script_mem.max_ssize);
uint8_t slen = strlen(sp);
strlcpy(dp, sp, glob_script_mem.max_ssize);
sp += slen + 1;
}
}
#ifdef USE_SCRIPT_FATFS
if (!glob_script_mem.script_sd_found) {
#if USE_SCRIPT_FATFS>=0
// user sd card
fsp = &SD;
if (SD.begin(USE_SCRIPT_FATFS)) {
#else
// use flash file
#ifdef ESP32
// if (SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
if (FFat.begin(true)) {
#else
if (fsp->begin()) {
#endif // ESP32
#endif // USE_SCRIPT_FATFS>=0
glob_script_mem.script_sd_found = 1;
} else {
glob_script_mem.script_sd_found = 0;
}
}
for (uint8_t cnt = 0; cnt<SFS_MAX; cnt++) {
glob_script_mem.file_flags[cnt].is_open = 0;
}
#endif
#if SCRIPT_DEBUG>0
ClaimSerial();
SetSerialBaudrate(9600);
#endif //SCRIPT_DEBUG
// store start of actual program here
glob_script_mem.scriptptr = lp - 1;
glob_script_mem.scriptptr_bu = glob_script_mem.scriptptr;
#ifdef USE_SCRIPT_GLOBVARS
if (glob_script_mem.udp_flags.udp_used) {
Script_Init_UDP();
glob_script = Run_Scripter(">G", -2, 0);
}
#endif //USE_SCRIPT_GLOBVARS
return 0;
}
#ifdef USE_SCRIPT_FATFS
uint32_t get_fsinfo(uint32_t sel) {
uint32_t result = 0;
#ifdef ESP32
#if USE_SCRIPT_FATFS >=0
if (sel == 0) {
result = SD.totalBytes()/1000;
} else if (sel == 1) {
result = (SD.totalBytes() - SD.usedBytes())/1000;
}
#else
if (sel == 0) {
result = FFat.totalBytes()/1000;
} else if (sel == 1) {
result = FFat.freeBytes()/1000;
}
#endif // USE_SCRIPT_FATFS>=0
#else
// ESP8266
FSInfo64 fsinfo;
fsp->info64(fsinfo);
if (sel == 0) {
result = fsinfo.totalBytes/1000;
} else if (sel == 1) {
result = (fsinfo.totalBytes - fsinfo.usedBytes)/1000;
}
#endif // ESP32
return result;
}
// format number with thousand marker
void form1000(uint32_t number, char *dp, char sc) {
char str[32];
sprintf(str, "%d", number);
char *sp = str;
uint32_t inum = strlen(sp)/3;
uint32_t fnum = strlen(sp)%3;
if (!fnum) inum--;
for (uint32_t count=0; count<=inum; count++) {
if (fnum){
memcpy(dp,sp,fnum);
dp+=fnum;
sp+=fnum;
fnum=0;
} else {
memcpy(dp,sp,3);
dp+=3;
sp+=3;
}
if (count!=inum) {
*dp++=sc;
}
}
*dp=0;
}
#endif //USE_SCRIPT_FATFS
#ifdef USE_SCRIPT_GLOBVARS
#define SCRIPT_UDP_BUFFER_SIZE 128
#define SCRIPT_UDP_PORT 1999
IPAddress script_udp_remote_ip;
void Script_Stop_UDP(void) {
if (!glob_script_mem.udp_flags.udp_used) return;
if (glob_script_mem.udp_flags.udp_connected) {
Script_PortUdp.flush();
Script_PortUdp.stop();
glob_script_mem.udp_flags.udp_connected = 0;
}
}
void Script_Init_UDP() {
if (global_state.network_down) return;
if (!glob_script_mem.udp_flags.udp_used) return;
if (glob_script_mem.udp_flags.udp_connected) return;
if (Script_PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), SCRIPT_UDP_PORT)) {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP "SCRIPT UDP started"));
glob_script_mem.udp_flags.udp_connected = 1;
} else {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP "SCRIPT UDP failed"));
glob_script_mem.udp_flags.udp_connected = 0;
}
}
void Script_PollUdp(void) {
if (global_state.network_down) return;
if (!glob_script_mem.udp_flags.udp_used) return;
if (glob_script_mem.udp_flags.udp_connected ) {
while (Script_PortUdp.parsePacket()) {
char packet_buffer[SCRIPT_UDP_BUFFER_SIZE];
int32_t len = Script_PortUdp.read(packet_buffer, SCRIPT_UDP_BUFFER_SIZE - 1);
packet_buffer[len] = 0;
script_udp_remote_ip = Script_PortUdp.remoteIP();
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("UDP: Packet %s - %d - %s"), packet_buffer, len, script_udp_remote_ip.toString().c_str());
char *lp=packet_buffer;
if (!strncmp(lp,"=>", 2)) {
lp += 2;
char *cp=strchr(lp, '=');
if (cp) {
char vnam[32];
for (uint32_t count = 0; count<len; count++) {
if (lp[count]=='=') {
vnam[count] = 0;
break;
}
vnam[count] = lp[count];
}
float *fp;
char *sp;
uint32_t index;
uint32_t res = match_vars(vnam, &fp, &sp, &index);
if (res == NUM_RES) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("num var found - %s - %d - %d"), vnam, res, index);
*fp=CharToFloat(cp + 1);
} else if (res == STR_RES) {
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("string var found - %s - %d - %d"), vnam, res, index);
strlcpy(sp, cp + 1, SCRIPT_MAXSSIZE);
} else {
// error var not found
}
if (res) {
// mark changed
last_udp_ip = Script_PortUdp.remoteIP();
glob_script_mem.type[index].bits.changed = 1;
if (glob_script==99) {
Run_Scripter(">G", 2, 0);
}
}
}
}
optimistic_yield(100);
}
} else {
Script_Init_UDP();
}
}
void script_udp_sendvar(char *vname,float *fp,char *sp) {
if (!glob_script_mem.udp_flags.udp_used) return;
if (!glob_script_mem.udp_flags.udp_connected) return;
char sbuf[SCRIPT_MAXSSIZE + 4];
strcpy(sbuf, "=>");
strcat(sbuf, vname);
strcat(sbuf, "=");
if (fp) {
char flstr[16];
dtostrfd(*fp, 8, flstr);
strcat(sbuf, flstr);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("num var updated - %s"), sbuf);
} else {
strcat(sbuf, sp);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("string var updated - %s"), sbuf);
}
Script_PortUdp.beginPacket(IPAddress(239, 255, 255, 250), SCRIPT_UDP_PORT);
// Udp.print(String("RET UC: ") + String(recv_Packet));
Script_PortUdp.write((const uint8_t*)sbuf, strlen(sbuf));
Script_PortUdp.endPacket();
}
#endif //USE_SCRIPT_GLOBVARS
#ifdef USE_LIGHT
#ifdef USE_WS2812
void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) {
Ws2812ForceSuspend();
for (uint32_t cnt = 0; cnt<len; cnt++) {
uint32_t index = cnt + offset;
if (index>Settings.light_pixels) break;
uint32_t col = array[cnt];
Ws2812SetColor(index + 1, col>>16, col>>8, col, 0);
}
Ws2812ForceUpdate();
}
#endif //USE_WS2812
#endif //USE_LIGHT
float median_array(float *array, uint16_t len) {
uint8_t ind[len];
uint8_t mind = 0;
uint8_t index = 0;
uint8_t flg;
float min = FLT_MAX;
for (uint8_t hcnt = 0; hcnt<len/2+1; hcnt++) {
for (uint8_t mcnt = 0; mcnt<len; mcnt++) {
flg = 0;
for (uint8_t icnt = 0; icnt<index; icnt++) {
if (ind[icnt] == mcnt) {
flg = 1;
}
}
if (!flg) {
if (array[mcnt]<min) {
min = array[mcnt];
mind = mcnt;
}
}
}
ind[index] = mind;
index++;
min = FLT_MAX;
}
return array[ind[len / 2]];
}
float *Get_MFAddr(uint8_t index, uint16_t *len, uint16_t *ipos) {
*len = 0;
uint8_t *mp = (uint8_t*)glob_script_mem.mfilt;
for (uint8_t count = 0; count<MAXFILT; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
if (count==index) {
*len = mflp->numvals & AND_FILT_MASK;
if (ipos) *ipos = mflp->index;
return mflp->rbuff;
}
mp += sizeof(struct M_FILT) + ((mflp->numvals & AND_FILT_MASK) - 1) * sizeof(float);
}
return 0;
}
char *isvar(char *lp, uint8_t *vtype, struct T_INDEX *tind, float *fp, char *sp, JsonParserObject *jo);
float *get_array_by_name(char *name, uint16_t *alen) {
struct T_INDEX ind;
uint8_t vtype;
isvar(name, &vtype, &ind, 0, 0, 0);
if (vtype==VAR_NV) return 0;
if (vtype&STYPE) return 0;
uint16_t index = glob_script_mem.type[ind.index].index;
if (glob_script_mem.type[ind.index].bits.is_filter) {
float *fa = Get_MFAddr(index, alen, 0);
return fa;
}
return 0;
}
float Get_MFVal(uint8_t index, int16_t bind) {
uint8_t *mp = (uint8_t*)glob_script_mem.mfilt;
for (uint8_t count = 0; count<MAXFILT; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
if (count==index) {
uint16_t maxind = mflp->numvals & AND_FILT_MASK;
if (!bind) {
return mflp->index;
}
if (bind<0) {
return maxind;
}
if (bind<1 || bind>maxind) bind = maxind;
return mflp->rbuff[bind - 1];
}
mp += sizeof(struct M_FILT) + ((mflp->numvals & AND_FILT_MASK) - 1) * sizeof(float);
}
return 0;
}
void Set_MFVal(uint8_t index, uint16_t bind, float val) {
uint8_t *mp = (uint8_t*)glob_script_mem.mfilt;
for (uint8_t count = 0; count<MAXFILT; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
if (count==index) {
uint16_t maxind = mflp->numvals & AND_FILT_MASK;
if (!bind) {
mflp->index = val;
} else {
if (bind<1 || bind>maxind) bind = maxind;
mflp->rbuff[bind-1] = val;
}
return;
}
mp += sizeof(struct M_FILT) + ((mflp->numvals & AND_FILT_MASK) - 1) * sizeof(float);
}
}
float Get_MFilter(uint8_t index) {
uint8_t *mp = (uint8_t*)glob_script_mem.mfilt;
for (uint8_t count = 0; count<MAXFILT; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals & OR_FILT_MASK) {
// moving average
return mflp->maccu / (mflp->numvals & AND_FILT_MASK);
} else {
// median, sort array indices
return median_array(mflp->rbuff, mflp->numvals);
}
}
mp += sizeof(struct M_FILT) + ((mflp->numvals & AND_FILT_MASK) - 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; count<MAXFILT; count++) {
struct M_FILT *mflp = (struct M_FILT*)mp;
if (count==index) {
if (mflp->numvals & OR_FILT_MASK) {
// moving average
mflp->maccu -= mflp->rbuff[mflp->index];
mflp->maccu += invar;
mflp->rbuff[mflp->index] = invar;
mflp->index++;
if (mflp->index>=(mflp->numvals&AND_FILT_MASK)) 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 & AND_FILT_MASK) - 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];
mf->buffer[mf->index] = in;
mf->index++;
if (mf->index>=MEDIAN_SIZE) mf->index = 0;
return median_array(mf->buffer, MEDIAN_SIZE);
}
#ifdef USE_LIGHT
uint32_t HSVToRGB(uint16_t hue, uint8_t saturation, uint8_t value) {
float r = 0, g = 0, b = 0;
struct HSV {
float H;
float S;
float V;
} hsv;
hsv.H = hue;
hsv.S = (float)saturation / 100.0;
hsv.V = (float)value / 100.0;
if (hsv.S == 0) {
r = hsv.V;
g = hsv.V;
b = hsv.V;
} else {
int i;
float f, p, q, t;
if (hsv.H == 360)
hsv.H = 0;
else
hsv.H = hsv.H / 60;
i = (int)trunc(hsv.H);
f = hsv.H - i;
p = hsv.V * (1.0 - hsv.S);
q = hsv.V * (1.0 - (hsv.S * f));
t = hsv.V * (1.0 - (hsv.S * (1.0 - f)));
switch (i)
{
case 0:
r = hsv.V;
g = t;
b = p;
break;
case 1:
r = q;
g = hsv.V;
b = p;
break;
case 2:
r = p;
g = hsv.V;
b = t;
break;
case 3:
r = p;
g = q;
b = hsv.V;
break;
case 4:
r = t;
g = p;
b = hsv.V;
break;
default:
r = hsv.V;
g = p;
b = q;
break;
}
}
uint8_t ir, ig, ib;
ir = r * 255;
ig = g * 255;
ib = b * 255;
uint32_t rgb = (ir<<16) | (ig<<8) | ib;
return rgb;
}
#endif //USE_LIGHT
#ifdef USE_ANGLE_FUNC
uint32_t pulse_time_hl;
uint32_t pulse_time_lh;
uint32_t pulse_ltime_hl;
uint32_t pulse_ltime_lh;
uint8_t pt_pin;
#define MPT_DEBOUNCE 10
void ICACHE_RAM_ATTR MP_Timer(void) {
uint32_t level = digitalRead(pt_pin&0x3f);
uint32_t ms = millis();
uint32_t time;
if (level) {
// rising edge
pulse_ltime_lh = ms;
time = ms - pulse_ltime_hl;
if (time>MPT_DEBOUNCE) pulse_time_hl = time;
} else {
// falling edge
pulse_ltime_hl = ms;
time = ms - pulse_ltime_lh;
if (time>MPT_DEBOUNCE) pulse_time_lh = time;
}
}
uint32_t MeasurePulseTime(int32_t in) {
if (in >= 0) {
// define pin;
pt_pin = in;
pinMode(pt_pin & 0x3f, INPUT_PULLUP);
attachInterrupt(pt_pin & 0x3f, MP_Timer, CHANGE);
pulse_ltime_lh = millis();
pulse_ltime_hl = millis();
return 0;
}
uint32_t ptime;
if (in==-1) {
ptime = pulse_time_lh;
pulse_time_lh = 0;
} else {
ptime = pulse_time_hl;
pulse_time_hl = 0;
}
return ptime;
}
#endif // USE_ANGLE_FUNC
#ifdef USE_SCRIPT_GLOBVARS
uint32_t match_vars(char *dvnam, float **fp, char **sp, uint32_t *ind) {
uint16_t olen = strlen(dvnam);
struct T_INDEX *vtp = glob_script_mem.type;
for (uint32_t count = 0; count<glob_script_mem.numvars; count++) {
char *cp = glob_script_mem.glob_vnp + glob_script_mem.vnp_offset[count];
uint8_t slen = strlen(cp);
if (slen==olen && *cp==dvnam[0]) {
if (!strncmp(cp, dvnam, olen)) {
uint8_t index = vtp[count].index;
if (vtp[count].bits.is_string==0) {
if (vtp[count].bits.is_filter) {
// error
return 0;
} else {
*fp = &glob_script_mem.fvars[index];
*ind = count;
return NUM_RES;
}
} else {
*sp = glob_script_mem.glob_snp + (index * glob_script_mem.max_ssize);
*ind = count;
return STR_RES;
}
}
}
}
return 0;
}
#endif //USE_SCRIPT_GLOBVARS
char *isargs(char *lp, uint32_t isind) {
float fvar;
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
SCRIPT_SKIP_SPACES
if (*lp!='"') {
return lp;
}
lp++;
if (glob_script_mem.si_num[isind]>0 && glob_script_mem.last_index_string[isind]) {
free(glob_script_mem.last_index_string[isind]);
}
char *sstart = lp;
uint8_t slen = 0;
for (uint32_t cnt = 0; cnt<256; cnt++) {
if (*lp=='\n' || *lp=='"' || *lp==0) {
lp++;
if (cnt>0 && !slen) {
slen++;
}
glob_script_mem.siro_num[isind] = slen;
break;
}
if (*lp=='|') {
slen++;
}
lp++;
}
glob_script_mem.si_num[isind] = fvar;
if (glob_script_mem.si_num[isind]>0) {
if (glob_script_mem.si_num[isind]>MAX_SARRAY_NUM) {
glob_script_mem.si_num[isind] = MAX_SARRAY_NUM;
}
glob_script_mem.last_index_string[isind] = (char*)calloc(glob_script_mem.max_ssize*glob_script_mem.si_num[isind], 1);
for (uint32_t cnt = 0; cnt<glob_script_mem.siro_num[isind]; cnt++) {
char str[SCRIPT_MAXSSIZE];
GetTextIndexed(str, sizeof(str), cnt, sstart);
strlcpy(glob_script_mem.last_index_string[isind] + (cnt*glob_script_mem.max_ssize), str,glob_script_mem.max_ssize);
}
} else {
glob_script_mem.last_index_string[isind] = sstart;
}
lp++;
return lp;
}
char *isget(char *lp, char *sp, uint32_t isind) {
float fvar;
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
SCRIPT_SKIP_SPACES
char str[SCRIPT_MAXSSIZE];
str[0] = 0;
uint8_t index = fvar;
if (index<1) index = 1;
index--;
last_sindex = index;
glob_script_mem.sind_num = isind;
if (glob_script_mem.last_index_string[isind]) {
if (!glob_script_mem.si_num[isind]) {
if (index<=glob_script_mem.siro_num[isind]) {
GetTextIndexed(str, sizeof(str), index , glob_script_mem.last_index_string[isind]);
}
} else {
if (index>glob_script_mem.si_num[isind]) {
index = glob_script_mem.si_num[isind];
}
strlcpy(str,glob_script_mem.last_index_string[isind] + (index * glob_script_mem.max_ssize), glob_script_mem.max_ssize);
}
}
lp++;
if (sp) strlcpy(sp, str, glob_script_mem.max_ssize);
return lp;
}
// 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, JsonParserObject *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) {
if (*lp=='0' && *(lp+1)=='x') {
lp += 2;
*fp = strtol(lp, 0, 16);
} else {
*fp = CharToFloat(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;
uint8_t iob = *lp;
if (iob=='\\') {
lp++;
if (*lp=='t') {
iob = '\t';
} else if (*lp=='n') {
iob = '\n';
} else if (*lp=='r') {
iob = '\r';
} else if (*lp=='\\') {
iob = '\\';
} else {
lp--;
}
if (sp) *sp++ = iob;
} else {
if (sp) *sp++ = iob;
}
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 ])=+-/*%><!^&|}{";
for (count = 0; count<sizeof(vname); count++) {
char iob = lp[count];
if (!iob || strchr(term,iob)) {
vname[count] = 0;
break;
}
vname[count] = iob;
len += 1;
}
if (!vname[0]) {
// empty string
*vtype = VAR_NV;
tind->index = VAR_NV;
glob_script_mem.var_not_found = 1;
return lp;
}
struct T_INDEX *vtp = glob_script_mem.type;
char dvnam[32];
strcpy (dvnam, vname);
uint8_t olen = len;
last_findex = -1;
last_sindex = -1;
char *ja = strchr(dvnam, '[');
if (ja) {
*ja = 0;
ja++;
olen = strlen(dvnam);
}
for (count = 0; count<glob_script_mem.numvars; count++) {
char *cp = glob_script_mem.glob_vnp + glob_script_mem.vnp_offset[count];
uint8_t slen = strlen(cp);
if (slen==olen && *cp==dvnam[0]) {
if (!strncmp(cp, dvnam, olen)) {
uint8_t index = vtp[count].index;
*tind = vtp[count];
tind->index = count; // overwrite with global var index
if (vtp[count].bits.is_string==0) {
*vtype = NTYPE | index;
if (vtp[count].bits.is_filter) {
if (ja) {
lp += olen + 1;
lp = GetNumericArgument(lp, OPER_EQU, &fvar, 0);
last_findex = fvar;
fvar = Get_MFVal(index, fvar);
len = 1;
} else {
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
char jvname[32];
strcpy(jvname, vname);
const char* str_value;
uint8_t aindex;
String vn;
char *ja=strchr(jvname, '[');
if (ja) {
// json array
*ja = 0;
ja++;
// fetch array index
float fvar;
GetNumericArgument(ja, OPER_EQU, &fvar, 0);
aindex = fvar;
if (aindex<1 || aindex>6) aindex = 1;
aindex--;
}
if (jo->isValid()) {
char *subtype = strchr(jvname, '#');
char *subtype2;
if (subtype) {
*subtype = 0;
subtype++;
subtype2 = strchr(subtype, '#');
if (subtype2) {
*subtype2 = 0;
*subtype2++;
}
}
vn = jvname;
str_value = (*jo)[vn].getStr();
if ((*jo)[vn].isValid()) {
if (subtype) {
JsonParserObject jobj1 = (*jo)[vn];
if (jobj1.isValid()) {
vn = subtype;
jo = &jobj1;
str_value = (*jo)[vn].getStr();
if ((*jo)[vn].isValid()) {
// 2. stage
if (subtype2) {
JsonParserObject jobj2 = (*jo)[vn];
if ((*jo)[vn].isValid()) {
vn = subtype2;
jo = &jobj2;
str_value = (*jo)[vn].getStr();
if ((*jo)[vn].isValid()) {
goto skip;
} else {
goto chknext;
}
} else {
goto chknext;
}
}
// end
goto skip;
}
} else {
goto chknext;
}
}
skip:
if (ja) {
// json array
str_value = (*jo)[vn].getArray()[aindex].getStr();
}
if (str_value && *str_value) {
if ((*jo)[vn].isStr()) {
if (!strncmp(str_value, "ON", 2)) {
if (fp) *fp = 1;
goto nexit;
} else if (!strncmp(str_value, "OFF", 3)) {
if (fp) *fp = 0;
goto nexit;
} 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) {