mirror of https://github.com/arendst/Tasmota.git
7487 lines
219 KiB
C++
Executable File
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) {
|