mirror of https://github.com/arendst/Tasmota.git
Merge pull request #11399 from s-hadinger/berry_mar_20
Berry milestone March 20
This commit is contained in:
commit
d18604f25d
|
@ -4,24 +4,24 @@
|
||||||
|
|
||||||
void MPU6886::I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer){
|
void MPU6886::I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer){
|
||||||
|
|
||||||
myWire.beginTransmission(driver_Addr);
|
myWire->beginTransmission(driver_Addr);
|
||||||
myWire.write(start_Addr);
|
myWire->write(start_Addr);
|
||||||
myWire.endTransmission(false);
|
myWire->endTransmission(false);
|
||||||
uint8_t i = 0;
|
uint8_t i = 0;
|
||||||
myWire.requestFrom(driver_Addr,number_Bytes);
|
myWire->requestFrom(driver_Addr,number_Bytes);
|
||||||
|
|
||||||
//! Put read results in the Rx buffer
|
//! Put read results in the Rx buffer
|
||||||
while (myWire.available()) {
|
while (myWire->available()) {
|
||||||
read_Buffer[i++] = myWire.read();
|
read_Buffer[i++] = myWire->read();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MPU6886::I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer){
|
void MPU6886::I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer){
|
||||||
|
|
||||||
myWire.beginTransmission(driver_Addr);
|
myWire->beginTransmission(driver_Addr);
|
||||||
myWire.write(start_Addr);
|
myWire->write(start_Addr);
|
||||||
myWire.write(*write_Buffer);
|
myWire->write(*write_Buffer);
|
||||||
myWire.endTransmission();
|
myWire->endTransmission();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,9 +71,9 @@ class MPU6886 {
|
||||||
public:
|
public:
|
||||||
MPU6886(void) {};
|
MPU6886(void) {};
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
void setBus(uint32_t _bus) { myWire = _bus ? Wire1 : Wire; };
|
void setBus(uint32_t _bus) { myWire = _bus ? &Wire1 : &Wire; };
|
||||||
#else
|
#else
|
||||||
void setBus(uint32_t _bus) { myWire = Wire; };
|
void setBus(uint32_t _bus) { myWire = &Wire; };
|
||||||
#endif
|
#endif
|
||||||
int Init(void);
|
int Init(void);
|
||||||
void getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az);
|
void getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az);
|
||||||
|
@ -93,7 +93,7 @@ class MPU6886 {
|
||||||
// void getAhrsData(float *pitch,float *roll,float *yaw);
|
// void getAhrsData(float *pitch,float *roll,float *yaw);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TwoWire & myWire = Wire; // default to Wire (bus 0)
|
TwoWire * myWire = &Wire; // default to Wire (bus 0)
|
||||||
float aRes, gRes;
|
float aRes, gRes;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -19,7 +19,7 @@ be_define_native_module(energy, NULL);
|
||||||
#else
|
#else
|
||||||
/* @const_object_info_begin
|
/* @const_object_info_begin
|
||||||
module tasmota (scope: global, depend: 1) {
|
module tasmota (scope: global, depend: 1) {
|
||||||
getfreeheap, func(l_getFreeHeap)
|
get_free_heap, func(l_getFreeHeap)
|
||||||
}
|
}
|
||||||
@const_object_info_end */
|
@const_object_info_end */
|
||||||
#include "../generate/be_fixed_tasmota.h"
|
#include "../generate/be_fixed_tasmota.h"
|
||||||
|
|
|
@ -27,6 +27,8 @@ extern int l_getpower(bvm *vm);
|
||||||
extern int l_setlight(bvm *vm);
|
extern int l_setlight(bvm *vm);
|
||||||
extern int l_setpower(bvm *vm);
|
extern int l_setpower(bvm *vm);
|
||||||
|
|
||||||
|
extern int l_i2cenabled(bvm *vm);
|
||||||
|
|
||||||
// #if !BE_USE_PRECOMPILED_OBJECT
|
// #if !BE_USE_PRECOMPILED_OBJECT
|
||||||
#if 1 // TODO we will do pre-compiled later
|
#if 1 // TODO we will do pre-compiled later
|
||||||
// Class definition
|
// Class definition
|
||||||
|
@ -39,27 +41,30 @@ void be_load_tasmota_ntvlib(bvm *vm)
|
||||||
{ "_rules", NULL },
|
{ "_rules", NULL },
|
||||||
{ "_timers", NULL },
|
{ "_timers", NULL },
|
||||||
{ "_cmd", NULL },
|
{ "_cmd", NULL },
|
||||||
{ "getfreeheap", l_getFreeHeap },
|
{ "_drivers", NULL },
|
||||||
|
{ "get_free_heap", l_getFreeHeap },
|
||||||
{ "publish", l_publish },
|
{ "publish", l_publish },
|
||||||
{ "cmd", l_cmd },
|
{ "cmd", l_cmd },
|
||||||
{ "getoption", l_getoption },
|
{ "get_option", l_getoption },
|
||||||
{ "millis", l_millis },
|
{ "millis", l_millis },
|
||||||
{ "timereached", l_timereached },
|
{ "time_reached", l_timereached },
|
||||||
{ "yield", l_yield },
|
{ "yield", l_yield },
|
||||||
{ "delay", l_delay },
|
{ "delay", l_delay },
|
||||||
{ "scaleuint", l_scaleuint },
|
{ "scale_uint", l_scaleuint },
|
||||||
|
|
||||||
{ "respcmnd", l_respCmnd },
|
{ "resp_cmnd", l_respCmnd },
|
||||||
{ "respcmndstr", l_respCmndStr },
|
{ "resp_cmnd_str", l_respCmndStr },
|
||||||
{ "respcmnd_done", l_respCmndDone },
|
{ "resp_cmnd_done", l_respCmndDone },
|
||||||
{ "respcmnd_error", l_respCmndError },
|
{ "resp_cmnd_error", l_respCmndError },
|
||||||
{ "respcmnd_failed", l_respCmndFailed },
|
{ "resp_cmnd_failed", l_respCmndFailed },
|
||||||
{ "resolvecmnd", l_resolveCmnd },
|
{ "resolvecmnd", l_resolveCmnd },
|
||||||
|
|
||||||
{ "getlight", l_getlight },
|
{ "get_light", l_getlight },
|
||||||
{ "getpower", l_getpower },
|
{ "get_power", l_getpower },
|
||||||
{ "setlight", l_setlight },
|
{ "set_light", l_setlight },
|
||||||
{ "setpower", l_setpower },
|
{ "set_power", l_setpower },
|
||||||
|
|
||||||
|
{ "i2c_enabled", l_i2cenabled },
|
||||||
|
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
@ -69,7 +74,7 @@ void be_load_tasmota_ntvlib(bvm *vm)
|
||||||
#else
|
#else
|
||||||
/* @const_object_info_begin
|
/* @const_object_info_begin
|
||||||
module tasmota (scope: global, depend: 1) {
|
module tasmota (scope: global, depend: 1) {
|
||||||
getfreeheap, func(l_getFreeHeap)
|
get_free_heap, func(l_getFreeHeap)
|
||||||
}
|
}
|
||||||
@const_object_info_end */
|
@const_object_info_end */
|
||||||
#include "../generate/be_fixed_tasmota.h"
|
#include "../generate/be_fixed_tasmota.h"
|
||||||
|
|
|
@ -20,6 +20,9 @@ extern int b_wire_scan(bvm *vm);
|
||||||
|
|
||||||
extern int b_wire_validwrite(bvm *vm);
|
extern int b_wire_validwrite(bvm *vm);
|
||||||
extern int b_wire_validread(bvm *vm);
|
extern int b_wire_validread(bvm *vm);
|
||||||
|
extern int b_wire_readbytes(bvm *vm);
|
||||||
|
extern int b_wire_writebytes(bvm *vm);
|
||||||
|
extern int b_wire_detect(bvm *vm);
|
||||||
|
|
||||||
// #if !BE_USE_PRECOMPILED_OBJECT
|
// #if !BE_USE_PRECOMPILED_OBJECT
|
||||||
#if 1 // TODO we will do pre-compiled later
|
#if 1 // TODO we will do pre-compiled later
|
||||||
|
@ -28,15 +31,18 @@ void be_load_wirelib(bvm *vm)
|
||||||
static const bnfuncinfo members[] = {
|
static const bnfuncinfo members[] = {
|
||||||
{ "_bus", NULL }, // bus number
|
{ "_bus", NULL }, // bus number
|
||||||
{ "init", b_wire_init },
|
{ "init", b_wire_init },
|
||||||
{ "_begintransmission", b_wire_begintransmission },
|
{ "_begin_transmission", b_wire_begintransmission },
|
||||||
{ "_endtransmission", b_wire_endtransmission },
|
{ "_end_transmission", b_wire_endtransmission },
|
||||||
{ "_requestfrom", b_wire_requestfrom },
|
{ "_request_from", b_wire_requestfrom },
|
||||||
{ "_available", b_wire_available },
|
{ "_available", b_wire_available },
|
||||||
{ "_write", b_wire_write },
|
{ "_write", b_wire_write },
|
||||||
{ "_read", b_wire_read },
|
{ "_read", b_wire_read },
|
||||||
{ "scan", b_wire_scan },
|
{ "scan", b_wire_scan },
|
||||||
{ "write", b_wire_validwrite },
|
{ "write", b_wire_validwrite },
|
||||||
{ "read", b_wire_validread },
|
{ "read", b_wire_validread },
|
||||||
|
{ "read_bytes", b_wire_validread },
|
||||||
|
{ "write_bytes", b_wire_validread },
|
||||||
|
{ "detect", b_wire_detect },
|
||||||
|
|
||||||
{ NULL, NULL }
|
{ NULL, NULL }
|
||||||
};
|
};
|
||||||
|
@ -45,7 +51,7 @@ void be_load_wirelib(bvm *vm)
|
||||||
#else
|
#else
|
||||||
/* @const_object_info_begin
|
/* @const_object_info_begin
|
||||||
module tasmota (scope: global, depend: 1) {
|
module tasmota (scope: global, depend: 1) {
|
||||||
getfreeheap, func(l_getFreeHeap)
|
get_free_heap, func(l_getFreeHeap)
|
||||||
}
|
}
|
||||||
@const_object_info_end */
|
@const_object_info_end */
|
||||||
#include "../generate/be_fixed_tasmota.h"
|
#include "../generate/be_fixed_tasmota.h"
|
||||||
|
|
|
@ -162,11 +162,21 @@
|
||||||
* are not required.
|
* are not required.
|
||||||
* The default is to use the functions in the standard library.
|
* The default is to use the functions in the standard library.
|
||||||
**/
|
**/
|
||||||
|
#ifdef USE_BERRY_PSRAM
|
||||||
|
extern void *special_malloc(uint32_t size);
|
||||||
|
extern void *special_realloc(void *ptr, size_t size);
|
||||||
|
#define BE_EXPLICIT_MALLOC special_malloc
|
||||||
|
#define BE_EXPLICIT_REALLOC special_realloc
|
||||||
|
#else
|
||||||
|
#define BE_EXPLICIT_MALLOC malloc
|
||||||
|
#define BE_EXPLICIT_REALLOC realloc
|
||||||
|
#endif // USE_BERRY_PSRAM
|
||||||
|
|
||||||
#define BE_EXPLICIT_ABORT abort
|
#define BE_EXPLICIT_ABORT abort
|
||||||
#define BE_EXPLICIT_EXIT exit
|
#define BE_EXPLICIT_EXIT exit
|
||||||
#define BE_EXPLICIT_MALLOC malloc
|
// #define BE_EXPLICIT_MALLOC malloc
|
||||||
#define BE_EXPLICIT_FREE free
|
#define BE_EXPLICIT_FREE free
|
||||||
#define BE_EXPLICIT_REALLOC realloc
|
// #define BE_EXPLICIT_REALLOC realloc
|
||||||
|
|
||||||
/* Macro: be_assert
|
/* Macro: be_assert
|
||||||
* Berry debug assertion. Only enabled when BE_DEBUG is active.
|
* Berry debug assertion. Only enabled when BE_DEBUG is active.
|
||||||
|
|
|
@ -10,11 +10,11 @@ runcolor = nil
|
||||||
def runcolor()
|
def runcolor()
|
||||||
var pwr = energy.read().find('activepower',0)
|
var pwr = energy.read().find('activepower',0)
|
||||||
print(pwr)
|
print(pwr)
|
||||||
var red = tasmota.scaleuint(int(pwr), 0, 2500, 0, 255)
|
var red = tasmota.scale_uint(int(pwr), 0, 2500, 0, 255)
|
||||||
var green = 255 - red
|
var green = 255 - red
|
||||||
var channels = [red, green, 0]
|
var channels = [red, green, 0]
|
||||||
tasmota.setlight({"channels":channels, "bri":64, "power":true})
|
tasmota.set_light({"channels":channels, "bri":64, "power":true})
|
||||||
tasmota.settimer(2000, runcolor)
|
tasmota.set_timer(2000, runcolor)
|
||||||
end
|
end
|
||||||
|
|
||||||
#- run animation -#
|
#- run animation -#
|
||||||
|
|
|
@ -1,211 +0,0 @@
|
||||||
import json import string
|
|
||||||
tasmota = module("tasmota")
|
|
||||||
def log(m) print(m) end
|
|
||||||
def save() end
|
|
||||||
|
|
||||||
#######
|
|
||||||
import string
|
|
||||||
import json
|
|
||||||
import gc
|
|
||||||
import tasmota
|
|
||||||
#// import alias
|
|
||||||
import tasmota as t
|
|
||||||
|
|
||||||
def charsinstring(s,c)
|
|
||||||
for i:0..size(s)-1
|
|
||||||
for j:0..size(c)-1
|
|
||||||
if s[i] == c[j] return i end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return -1
|
|
||||||
end
|
|
||||||
|
|
||||||
###
|
|
||||||
class Tasmota
|
|
||||||
var _op, _operators, _rules
|
|
||||||
def init()
|
|
||||||
self._operators = "=<>!|"
|
|
||||||
self._op = [
|
|
||||||
['==', /s1,s2-> str(s1) == str(s2)],
|
|
||||||
['!==',/s1,s2-> str(s1) != str(s2)],
|
|
||||||
['=', /f1,f2-> real(f1) == real(f2)],
|
|
||||||
['!=', /f1,f2-> real(f1) != real(f2)],
|
|
||||||
['>=', /f1,f2-> real(f1) >= real(f2)],
|
|
||||||
['<=', /f1,f2-> real(f1) <= real(f2)],
|
|
||||||
['>', /f1,f2-> real(f1) > real(f2)],
|
|
||||||
['<', /f1,f2-> real(f1) < real(f2)],
|
|
||||||
]
|
|
||||||
self._rules = {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
###
|
|
||||||
|
|
||||||
tasmota._eqstr=/s1,s2-> str(s1) == str(s2)
|
|
||||||
tasmota._neqstr=/s1,s2-> str(s1) != str(s2)
|
|
||||||
tasmota._eq=/f1,f2-> real(f1) == real(f2)
|
|
||||||
tasmota._neq=/f1,f2-> real(f1) != real(f2)
|
|
||||||
tasmota._gt=/f1,f2-> real(f1) > real(f2)
|
|
||||||
tasmota._lt=/f1,f2-> real(f1) < real(f2)
|
|
||||||
tasmota._ge=/f1,f2-> real(f1) >= real(f2)
|
|
||||||
tasmota._le=/f1,f2-> real(f1) <= real(f2)
|
|
||||||
tasmota._op=[
|
|
||||||
['==',tasmota._eqstr],
|
|
||||||
['!==',tasmota._neqstr],
|
|
||||||
['=',tasmota._eq],
|
|
||||||
['!=',tasmota._neq],
|
|
||||||
['>=',tasmota._ge],
|
|
||||||
['<=',tasmota._le],
|
|
||||||
['>',tasmota._gt],
|
|
||||||
['<',tasmota._lt],
|
|
||||||
]
|
|
||||||
tasmota._operators="=<>!|"
|
|
||||||
|
|
||||||
# split the item when there is an operator, returns a list of (left,op,right)
|
|
||||||
# ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
|
|
||||||
tasmota.find_op = def (item)
|
|
||||||
var pos = charsinstring(item, tasmota._operators)
|
|
||||||
if pos>=0
|
|
||||||
var op_split = string.split(item,pos)
|
|
||||||
#print(op_split)
|
|
||||||
var op_left = op_split[0]
|
|
||||||
var op_rest = op_split[1]
|
|
||||||
# iterate through operators
|
|
||||||
for op:tasmota._op
|
|
||||||
if string.find(op_rest,op[0]) == 0
|
|
||||||
var op_func = op[1]
|
|
||||||
var op_right = string.split(op_rest,size(op[0]))[1]
|
|
||||||
return [op_left,op_func,op_right]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return [item, nil, nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def findkeyi(m,keyi)
|
|
||||||
var keyu = string.toupper(keyi)
|
|
||||||
if classof(m) == map
|
|
||||||
for k:m.keys()
|
|
||||||
if string.toupper(k)==keyu || keyi=='?'
|
|
||||||
return k
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
tasmota.try_rule = def (ev, rule, f)
|
|
||||||
var rl_list = tasmota.find_op(rule)
|
|
||||||
var e=ev
|
|
||||||
var rl=string.split(rl_list[0],'#')
|
|
||||||
for it:rl
|
|
||||||
found=findkeyi(e,it)
|
|
||||||
if found == nil
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
e=e[found]
|
|
||||||
end
|
|
||||||
# check if condition is true
|
|
||||||
if rl_list[1]
|
|
||||||
# did we find a function
|
|
||||||
if !rl_list[1](e,rl_list[2])
|
|
||||||
# condition is not met
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f(e,ev)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
tasmota_rules={}
|
|
||||||
tasmota.rule = def(pat,f) tasmota_rules[pat] = f end
|
|
||||||
|
|
||||||
tasmota.exec_rules = def (ev_json)
|
|
||||||
var ev = json.load(ev_json)
|
|
||||||
var ret = false
|
|
||||||
if ev == nil
|
|
||||||
log('BRY: ERROR, bad json: '+ev_json, 3)
|
|
||||||
end
|
|
||||||
for r:tasmota_rules.keys()
|
|
||||||
ret = tasmota.try_rule(ev,r,tasmota_rules[r]) || ret
|
|
||||||
end
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
tasmota.delay = def(ms)
|
|
||||||
tend = tasmota.millis(ms)
|
|
||||||
while !tasmota.timereached(tend)
|
|
||||||
tasmota.yield()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def load(f)
|
|
||||||
try
|
|
||||||
if f[0] != '/' f = '/' + f end
|
|
||||||
compile(f,'file')()
|
|
||||||
except .. as e
|
|
||||||
log(string.format("BRY: could not load file '%s' - %s",f,e))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#- Test
|
|
||||||
#################################################################
|
|
||||||
|
|
||||||
def log(m) print(m) end
|
|
||||||
def my_rule(e,ev) log("e1="+str(e)+" e2="+str(ev)) end
|
|
||||||
|
|
||||||
tasmota.rule("ZBRECEIVED#?#LINKQUALITY", my_rule)
|
|
||||||
tasmota.rule("ZBRECEIVED#0x1234", my_rule)
|
|
||||||
|
|
||||||
tasmota.rule("ZBRECEIVED#?#LINKQUALITY<10", my_rule)
|
|
||||||
|
|
||||||
tasmota.rule("Dimmer>50", my_rule)
|
|
||||||
tasmota.rule("Dimmer=01", my_rule)
|
|
||||||
|
|
||||||
|
|
||||||
tasmota.rule("Color==022600", my_rule)
|
|
||||||
|
|
||||||
tasmota.exec_rules('{"Color":"022600"}')
|
|
||||||
|
|
||||||
tasmota.exec_rules('{"ZbReceived":{"0x1234":{"Device":"0x1234","LinkQuality":50}}}')
|
|
||||||
|
|
||||||
tasmota.exec_rules('{"Dimmer":10}')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# tasmota.rule("DIMMER", my_rule)
|
|
||||||
# tasmota.rule("DIMMER#DATA#DATA", my_rule)
|
|
||||||
# tasmota.exec_rules('{"Dimmer":{"Data":50}}')
|
|
||||||
|
|
||||||
|
|
||||||
-#
|
|
||||||
|
|
||||||
#-
|
|
||||||
tasmota.find_op("aaa")
|
|
||||||
tasmota.find_op("aaa>50")
|
|
||||||
-#
|
|
||||||
|
|
||||||
#-
|
|
||||||
# Example of backlog equivalent
|
|
||||||
|
|
||||||
def backlog(cmd_list)
|
|
||||||
delay_backlog = tasmota.getoption(34) # in milliseconds
|
|
||||||
delay = 0
|
|
||||||
for cmd:cmd_list
|
|
||||||
tasmota.timer(delay, /-> tasmota.cmd(cmd))
|
|
||||||
delay = delay + delay_backlog
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
br def backlog(cmd_list) delay_backlog = tasmota.getoption(34) delay = 0 for cmd:cmd_list tasmota.timer(delay, /-> tasmota.cmd(cmd)) delay = delay + delay_backlog end end
|
|
||||||
|
|
||||||
br backlog( [ "Power 0", "Status 4", "Power 1" ] )
|
|
||||||
|
|
||||||
-#
|
|
||||||
|
|
||||||
#-
|
|
||||||
|
|
||||||
tasmota.delay = def(ms) tend = tasmota.millis(ms) log(str(tasmota.millis())) while !tasmota.timereached(tend) end log(str(tasmota.millis())) end
|
|
||||||
tasmota.delay = def(ms) a=0 tend = tasmota.millis(ms) log(str(tasmota.millis())) while !tasmota.timereached(tend) a=a+1 end log(str(tasmota.millis())) log(str(a)) end
|
|
||||||
|
|
||||||
-#
|
|
|
@ -463,6 +463,11 @@
|
||||||
|
|
||||||
// #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code)
|
// #define SUPPORT_MQTT_EVENT // Support trigger event with MQTT subscriptions (+3k5 code)
|
||||||
|
|
||||||
|
// -- Berry Scripting Language - ESP32 only ----------------------------
|
||||||
|
// #define USE_BERRY // Enable Berry scripting language
|
||||||
|
#define USE_BERRY_PSRAM // Allocate Berry memory in PSRAM if PSRAM is connected - this might be slightly slower but leaves main memory intact
|
||||||
|
|
||||||
|
|
||||||
// -- Optional modules ----------------------------
|
// -- Optional modules ----------------------------
|
||||||
#define ROTARY_V1 // Add support for Rotary Encoder as used in MI Desk Lamp (+0k8 code)
|
#define ROTARY_V1 // Add support for Rotary Encoder as used in MI Desk Lamp (+0k8 code)
|
||||||
#define ROTARY_MAX_STEPS 10 // Rotary step boundary
|
#define ROTARY_MAX_STEPS 10 // Rotary step boundary
|
||||||
|
|
|
@ -74,6 +74,10 @@ void *special_malloc(uint32_t size) {
|
||||||
return malloc(size);
|
return malloc(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void *special_realloc(void *ptr, size_t size) {
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -461,6 +465,13 @@ void *special_malloc(uint32_t size) {
|
||||||
return malloc(size);
|
return malloc(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void *special_realloc(void *ptr, size_t size) {
|
||||||
|
if (psramFound()) {
|
||||||
|
return heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||||
|
} else {
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
xdrv_52_0_berry_struct.ino - Berry scripting language, native fucnctions
|
||||||
|
|
||||||
|
Copyright (C) 2021 Stephan Hadinger, Berry language by Guan Wenliang https://github.com/Skiars/berry
|
||||||
|
|
||||||
|
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_BERRY
|
||||||
|
|
||||||
|
#include <berry.h>
|
||||||
|
|
||||||
|
typedef LList_elt<char[0]> log_elt; // store the string after the header to avoid double allocation if we had used char*
|
||||||
|
|
||||||
|
class BerryLog {
|
||||||
|
public:
|
||||||
|
// typedef LList_elt<char[0]> log_elt; // store the string after the header to avoid double allocation if we had used char*
|
||||||
|
inline static size_t size(size_t chars) { return sizeof(log_elt) + chars; }
|
||||||
|
inline bool isEmpty(void) const { return log.isEmpty(); }
|
||||||
|
log_elt * addString(const char * s, const char * prefix = nullptr, const char * suffix = nullptr) {
|
||||||
|
if (suffix == nullptr) { suffix = ""; }
|
||||||
|
if (prefix == nullptr) { prefix = ""; }
|
||||||
|
if (s == nullptr) { s = ""; }
|
||||||
|
size_t s_len = strlen_P(s) + strlen_P(prefix) + strlen_P(suffix);
|
||||||
|
if (0 == s_len) { return nullptr; } // do nothing
|
||||||
|
log_elt * elt = (log_elt*) ::operator new(sizeof(log_elt) + s_len + 1); // use low-level new to specify the bytes size
|
||||||
|
snprintf_P((char*) &elt->val(), s_len+1, PSTR("%s%s%s"), prefix, s, suffix);
|
||||||
|
log.addToLast(elt);
|
||||||
|
return elt;
|
||||||
|
}
|
||||||
|
void reset(void) {
|
||||||
|
log.reset();
|
||||||
|
}
|
||||||
|
LList<char[0]> log;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BerrySupport {
|
||||||
|
public:
|
||||||
|
bvm *vm = nullptr; // berry vm
|
||||||
|
bool rules_busy = false; // are we already processing rules, avoid infinite loop
|
||||||
|
bool autoexec_done = false; // do we still need to load 'autoexec.be'
|
||||||
|
bool repl_active = false; // is REPL running (activates log recording)
|
||||||
|
// output log is stored as a LinkedList of buffers
|
||||||
|
// and active only when a REPL command is running
|
||||||
|
BerryLog log;
|
||||||
|
};
|
||||||
|
BerrySupport berry;
|
||||||
|
|
||||||
|
|
||||||
|
#endif // USE_BERRY
|
|
@ -23,6 +23,8 @@
|
||||||
#include <berry.h>
|
#include <berry.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
|
||||||
|
const uint32_t BERRY_MAX_LOGS = 16; // max number of print output recorded when outside of REPL, used to avoid infinite grow of logs
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Native functions mapped to Berry functions
|
* Native functions mapped to Berry functions
|
||||||
*
|
*
|
||||||
|
@ -30,18 +32,18 @@
|
||||||
*
|
*
|
||||||
* import tasmota
|
* import tasmota
|
||||||
*
|
*
|
||||||
* tasmota.getfreeheap() -> int
|
* tasmota.get_free_heap() -> int
|
||||||
* tasmota.publish(topic:string, payload:string[, retain:bool]) -> nil
|
* tasmota.publish(topic:string, payload:string[, retain:bool]) -> nil
|
||||||
* tasmota.cmd(command:string) -> string
|
* tasmota.cmd(command:string) -> string
|
||||||
* tasmota.getoption(index:int) -> int
|
* tasmota.get_option(index:int) -> int
|
||||||
* tasmota.millis([delay:int]) -> int
|
* tasmota.millis([delay:int]) -> int
|
||||||
* tasmota.timereached(timer:int) -> bool
|
* tasmota.time_reached(timer:int) -> bool
|
||||||
* tasmota.yield() -> nil
|
* tasmota.yield() -> nil
|
||||||
*
|
*
|
||||||
* tasmota.getlight([index:int = 0]) -> map
|
* tasmota.get_light([index:int = 0]) -> map
|
||||||
* tasmota.getpower([index:int = 0]) -> bool
|
* tasmota.get_power([index:int = 0]) -> bool
|
||||||
* tasmota.setpower(idx:int, power:bool) -> bool or nil
|
* tasmota.set_power(idx:int, power:bool) -> bool or nil
|
||||||
* tasmota.setlight(idx:int, values:map) -> map
|
* tasmota.set_light(idx:int, values:map) -> map
|
||||||
*
|
*
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -97,7 +99,7 @@ extern "C" {
|
||||||
be_raise(vm, kTypeError, nullptr);
|
be_raise(vm, kTypeError, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Berry: tasmota.getoption(index:int) -> int
|
// Berry: tasmota.get_option(index:int) -> int
|
||||||
//
|
//
|
||||||
int32_t l_getoption(struct bvm *vm);
|
int32_t l_getoption(struct bvm *vm);
|
||||||
int32_t l_getoption(struct bvm *vm) {
|
int32_t l_getoption(struct bvm *vm) {
|
||||||
|
@ -110,7 +112,7 @@ extern "C" {
|
||||||
be_raise(vm, kTypeError, nullptr);
|
be_raise(vm, kTypeError, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Berry: tasmota.timereached(timer:int) -> bool
|
// Berry: tasmota.time_reached(timer:int) -> bool
|
||||||
//
|
//
|
||||||
int32_t l_timereached(struct bvm *vm);
|
int32_t l_timereached(struct bvm *vm);
|
||||||
int32_t l_timereached(struct bvm *vm) {
|
int32_t l_timereached(struct bvm *vm) {
|
||||||
|
@ -145,7 +147,7 @@ extern "C" {
|
||||||
be_return_nil(vm);
|
be_return_nil(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Berry: tasmota.scaleuint(int * 5) -> int
|
// Berry: tasmota.scale_uint(int * 5) -> int
|
||||||
//
|
//
|
||||||
int32_t l_scaleuint(struct bvm *vm);
|
int32_t l_scaleuint(struct bvm *vm);
|
||||||
int32_t l_scaleuint(struct bvm *vm) {
|
int32_t l_scaleuint(struct bvm *vm) {
|
||||||
|
@ -470,6 +472,24 @@ extern "C" {
|
||||||
be_raise(vm, kTypeError, nullptr);
|
be_raise(vm, kTypeError, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_I2C
|
||||||
|
// I2C specific
|
||||||
|
// Berry: `i2c_enabled(index:int) -> bool` is I2C device enabled
|
||||||
|
int32_t l_i2cenabled(struct bvm *vm);
|
||||||
|
int32_t l_i2cenabled(struct bvm *vm) {
|
||||||
|
int32_t top = be_top(vm); // Get the number of arguments
|
||||||
|
if (top == 2 && be_isint(vm, 2)) {
|
||||||
|
int32_t index = be_toint(vm, 2);
|
||||||
|
bool enabled = I2cEnabled(index);
|
||||||
|
be_pushbool(vm, enabled);
|
||||||
|
be_return(vm); // Return
|
||||||
|
}
|
||||||
|
be_raise(vm, kTypeError, nullptr);
|
||||||
|
}
|
||||||
|
#else // USE_I2C
|
||||||
|
int32_t l_i2cenabled(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
|
#endif // USE_I2C
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -522,6 +542,13 @@ extern "C" {
|
||||||
// called as a replacement to Berry `print()`
|
// called as a replacement to Berry `print()`
|
||||||
void berry_log(const char * berry_buf);
|
void berry_log(const char * berry_buf);
|
||||||
void berry_log(const char * berry_buf) {
|
void berry_log(const char * berry_buf) {
|
||||||
|
if (berry.repl_active) {
|
||||||
|
if (berry.log.log.length() >= BERRY_MAX_LOGS) {
|
||||||
|
berry.log.log.remove(berry.log.log.head());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// AddLog(LOG_LEVEL_INFO, PSTR("[Add to log] %s"), berry_buf);
|
||||||
|
berry.log.addString(berry_buf, nullptr, "\n");
|
||||||
AddLog(LOG_LEVEL_INFO, PSTR("%s"), berry_buf);
|
AddLog(LOG_LEVEL_INFO, PSTR("%s"), berry_buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ int32_t getBus(bvm *vm) {
|
||||||
*
|
*
|
||||||
* import wire
|
* import wire
|
||||||
*
|
*
|
||||||
* wire.getfreeheap() -> int
|
* wire.get_free_heap() -> int
|
||||||
*
|
*
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -135,6 +135,8 @@ extern "C" {
|
||||||
int32_t b_wire_write(struct bvm *vm);
|
int32_t b_wire_write(struct bvm *vm);
|
||||||
int32_t b_wire_write(struct bvm *vm) {
|
int32_t b_wire_write(struct bvm *vm) {
|
||||||
int32_t top = be_top(vm); // Get the number of arguments
|
int32_t top = be_top(vm); // Get the number of arguments
|
||||||
|
const void * buf;
|
||||||
|
size_t len;
|
||||||
TwoWire & myWire = getWire(vm);
|
TwoWire & myWire = getWire(vm);
|
||||||
if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2))) {
|
if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2))) {
|
||||||
if (be_isint(vm, 2)) {
|
if (be_isint(vm, 2)) {
|
||||||
|
@ -143,6 +145,8 @@ extern "C" {
|
||||||
} else if (be_isstring(vm, 2)) {
|
} else if (be_isstring(vm, 2)) {
|
||||||
const char * s = be_tostring(vm, 1);
|
const char * s = be_tostring(vm, 1);
|
||||||
myWire.write(s);
|
myWire.write(s);
|
||||||
|
} else if ((buf = be_tobytes(vm, 2, &len)) != nullptr) {
|
||||||
|
myWire.write((uint8_t*) buf, len);
|
||||||
} else {
|
} else {
|
||||||
be_return_nil(vm);
|
be_return_nil(vm);
|
||||||
}
|
}
|
||||||
|
@ -211,7 +215,7 @@ extern "C" {
|
||||||
uint8_t addr = be_toint(vm, 2);
|
uint8_t addr = be_toint(vm, 2);
|
||||||
uint8_t reg = be_toint(vm, 3);
|
uint8_t reg = be_toint(vm, 3);
|
||||||
uint8_t size = be_toint(vm, 4);
|
uint8_t size = be_toint(vm, 4);
|
||||||
bool ok = I2cValidRead(addr, reg, size); // TODO
|
bool ok = I2cValidRead(addr, reg, size, bus); // TODO
|
||||||
if (ok) {
|
if (ok) {
|
||||||
be_pushint(vm, i2c_buffer);
|
be_pushint(vm, i2c_buffer);
|
||||||
} else {
|
} else {
|
||||||
|
@ -221,6 +225,62 @@ extern "C" {
|
||||||
}
|
}
|
||||||
be_raise(vm, kTypeError, nullptr);
|
be_raise(vm, kTypeError, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Berry: `read_bytes(address:int, reg:int, size:int) -> bytes() or nil`
|
||||||
|
int32_t b_wire_readbytes(struct bvm *vm);
|
||||||
|
int32_t b_wire_readbytes(struct bvm *vm) {
|
||||||
|
// int32_t top = be_top(vm); // Get the number of arguments
|
||||||
|
// int32_t bus = getBus(vm);
|
||||||
|
// if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
|
||||||
|
// uint8_t addr = be_toint(vm, 2);
|
||||||
|
// uint8_t reg = be_toint(vm, 3);
|
||||||
|
// uint8_t size = be_toint(vm, 4);
|
||||||
|
// bool ok = I2cValidRead(addr, reg, size, bus); // TODO
|
||||||
|
// if (ok) {
|
||||||
|
// be_pushint(vm, i2c_buffer);
|
||||||
|
// } else {
|
||||||
|
// be_pushnil(vm);
|
||||||
|
// }
|
||||||
|
// be_return(vm); // Return
|
||||||
|
// }
|
||||||
|
be_raise(vm, kTypeError, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berry: `write_bytes(address:int, reg:int, val:bytes()) -> bool or nil`
|
||||||
|
int32_t b_wire_writebytes(struct bvm *vm);
|
||||||
|
int32_t b_wire_writebytes(struct bvm *vm) {
|
||||||
|
// int32_t top = be_top(vm); // Get the number of arguments
|
||||||
|
// int32_t bus = getBus(vm);
|
||||||
|
// if (top == 4 && be_isint(vm, 2) && be_isint(vm, 3) && be_isint(vm, 4)) {
|
||||||
|
// uint8_t addr = be_toint(vm, 2);
|
||||||
|
// uint8_t reg = be_toint(vm, 3);
|
||||||
|
// uint8_t size = be_toint(vm, 4);
|
||||||
|
// bool ok = I2cValidRead(addr, reg, size, bus); // TODO
|
||||||
|
// if (ok) {
|
||||||
|
// be_pushint(vm, i2c_buffer);
|
||||||
|
// } else {
|
||||||
|
// be_pushnil(vm);
|
||||||
|
// }
|
||||||
|
// be_return(vm); // Return
|
||||||
|
// }
|
||||||
|
be_raise(vm, kTypeError, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berry: `find(address:int) -> bool` true if device responds
|
||||||
|
int32_t b_wire_detect(struct bvm *vm);
|
||||||
|
int32_t b_wire_detect(struct bvm *vm) {
|
||||||
|
int32_t top = be_top(vm); // Get the number of arguments
|
||||||
|
TwoWire & myWire = getWire(vm);
|
||||||
|
if (top == 2 && be_isint(vm, 2)) {
|
||||||
|
uint8_t addr = be_toint(vm, 2);
|
||||||
|
// check the presence of the device
|
||||||
|
myWire.beginTransmission((uint8_t)addr);
|
||||||
|
bool found = (0 == myWire.endTransmission());
|
||||||
|
be_pushbool(vm, found);
|
||||||
|
be_return(vm); // Return
|
||||||
|
}
|
||||||
|
be_raise(vm, kTypeError, nullptr);
|
||||||
|
}
|
||||||
#else // USE_I2C
|
#else // USE_I2C
|
||||||
//
|
//
|
||||||
int32_t b_wire_i2cmissing(struct bvm *vm);
|
int32_t b_wire_i2cmissing(struct bvm *vm);
|
||||||
|
@ -239,6 +299,9 @@ extern "C" {
|
||||||
int32_t b_wire_scan(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
int32_t b_wire_scan(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
int32_t b_wire_validwrite(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
int32_t b_wire_validwrite(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
int32_t b_wire_validread(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
int32_t b_wire_validread(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
|
int32_t b_wire_readbytes(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
|
int32_t b_wire_writebytes(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
|
int32_t b_wire_detect(struct bvm *vm) __attribute__ ((weak, alias ("b_wire_i2cmissing")));
|
||||||
#endif // USE_I2C
|
#endif // USE_I2C
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,14 +55,11 @@ const char berry_prog[] =
|
||||||
"/f1,f2-> real(f1) < real(f2),"
|
"/f1,f2-> real(f1) < real(f2),"
|
||||||
"] "
|
"] "
|
||||||
"self._operators = \"=<>!|\" "
|
"self._operators = \"=<>!|\" "
|
||||||
"self._rules = {} "
|
|
||||||
"self._timers = [] "
|
|
||||||
"self._cmd = {} "
|
|
||||||
"end "
|
"end "
|
||||||
// add `charsinstring(s:string,c:string) -> int``
|
// add `chars_in_string(s:string,c:string) -> int``
|
||||||
// looks for any char in c, and return the position of the first chat
|
// looks for any char in c, and return the position of the first chat
|
||||||
// or -1 if not found
|
// or -1 if not found
|
||||||
"def charsinstring(s,c) "
|
"def chars_in_string(s,c) "
|
||||||
"for i:0..size(s)-1 "
|
"for i:0..size(s)-1 "
|
||||||
"for j:0..size(c)-1 "
|
"for j:0..size(c)-1 "
|
||||||
"if s[i] == c[j] return i end "
|
"if s[i] == c[j] return i end "
|
||||||
|
@ -72,7 +69,7 @@ const char berry_prog[] =
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
// find a key in map, case insensitive, return actual key or nil if not found
|
// find a key in map, case insensitive, return actual key or nil if not found
|
||||||
"def findkeyi(m,keyi) "
|
"def find_key_i(m,keyi) "
|
||||||
"import string "
|
"import string "
|
||||||
"var keyu = string.toupper(keyi) "
|
"var keyu = string.toupper(keyi) "
|
||||||
"if classof(m) == map "
|
"if classof(m) == map "
|
||||||
|
@ -84,14 +81,11 @@ const char berry_prog[] =
|
||||||
"end "
|
"end "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
// Rules
|
|
||||||
"def addrule(pat,f) self._rules[pat] = f end "
|
|
||||||
|
|
||||||
// # split the item when there is an operator, returns a list of (left,op,right)
|
// # split the item when there is an operator, returns a list of (left,op,right)
|
||||||
// # ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
|
// # ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"]
|
||||||
"def find_op(item) "
|
"def find_op(item) "
|
||||||
"import string "
|
"import string "
|
||||||
"var pos = self.charsinstring(item, self._operators) "
|
"var pos = self.chars_in_string(item, self._operators) "
|
||||||
"if pos>=0 "
|
"if pos>=0 "
|
||||||
"var op_split = string.split(item,pos) "
|
"var op_split = string.split(item,pos) "
|
||||||
// #print(op_split)
|
// #print(op_split)
|
||||||
|
@ -109,6 +103,14 @@ const char berry_prog[] =
|
||||||
"end "
|
"end "
|
||||||
"return [item, nil, nil] "
|
"return [item, nil, nil] "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
|
// Rules
|
||||||
|
"def add_rule(pat,f) "
|
||||||
|
"if !self._rules "
|
||||||
|
"self._rules={} "
|
||||||
|
"end "
|
||||||
|
"self._rules[pat] = f "
|
||||||
|
"end "
|
||||||
|
|
||||||
// Rules trigger if match. return true if match, false if not
|
// Rules trigger if match. return true if match, false if not
|
||||||
"def try_rule(ev, rule, f) "
|
"def try_rule(ev, rule, f) "
|
||||||
|
@ -117,7 +119,7 @@ const char berry_prog[] =
|
||||||
"var e=ev "
|
"var e=ev "
|
||||||
"var rl=string.split(rl_list[0],'#') "
|
"var rl=string.split(rl_list[0],'#') "
|
||||||
"for it:rl "
|
"for it:rl "
|
||||||
"found=self.findkeyi(e,it) "
|
"found=self.find_key_i(e,it) "
|
||||||
"if found == nil "
|
"if found == nil "
|
||||||
"return false "
|
"return false "
|
||||||
"end "
|
"end "
|
||||||
|
@ -138,55 +140,69 @@ const char berry_prog[] =
|
||||||
// Run rules, i.e. check each individual rule
|
// Run rules, i.e. check each individual rule
|
||||||
// Returns true if at least one rule matched, false if none
|
// Returns true if at least one rule matched, false if none
|
||||||
"def exec_rules(ev_json) "
|
"def exec_rules(ev_json) "
|
||||||
"import json "
|
"if self._rules "
|
||||||
"var ev = json.load(ev_json) "
|
"import json "
|
||||||
"var ret = false "
|
"var ev = json.load(ev_json) "
|
||||||
"if ev == nil "
|
"var ret = false "
|
||||||
"print('BRY: ERROR, bad json: '+ev_json, 3) "
|
"if ev == nil "
|
||||||
"else "
|
"print('BRY: ERROR, bad json: '+ev_json, 3) "
|
||||||
"for r: self._rules.keys() "
|
"else "
|
||||||
"ret = self.try_rule(ev,r,self._rules[r]) || ret "
|
"for r: self._rules.keys() "
|
||||||
|
"ret = self.try_rule(ev,r,self._rules[r]) || ret "
|
||||||
|
"end "
|
||||||
"end "
|
"end "
|
||||||
|
"return ret "
|
||||||
"end "
|
"end "
|
||||||
"return ret "
|
"return false "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
"def settimer(delay,f) self._timers.push([self.millis(delay),f]) end "
|
"def set_timer(delay,f) "
|
||||||
|
"if !self._timers self._timers=[] end "
|
||||||
|
"self._timers.push([self.millis(delay),f]) "
|
||||||
|
"end "
|
||||||
|
|
||||||
|
// run every 50ms tick
|
||||||
"def run_deferred() "
|
"def run_deferred() "
|
||||||
"var i=0 "
|
"if self._timers "
|
||||||
"while i<self._timers.size() "
|
"var i=0 "
|
||||||
"if self.timereached(self._timers[i][0]) "
|
"while i<self._timers.size() "
|
||||||
"f=self._timers[i][1] "
|
"if self.time_reached(self._timers[i][0]) "
|
||||||
"self._timers.remove(i) "
|
"f=self._timers[i][1] "
|
||||||
"f() "
|
"self._timers.remove(i) "
|
||||||
"else "
|
"f() "
|
||||||
"i=i+1 "
|
"else "
|
||||||
|
"i=i+1 "
|
||||||
|
"end "
|
||||||
"end "
|
"end "
|
||||||
"end "
|
"end "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
// Delay function, internally calls yield() every 10ms to avoid WDT
|
// // Delay function, internally calls yield() every 10ms to avoid WDT
|
||||||
"def delay(ms) "
|
// "def delay(ms) "
|
||||||
"var tend = self.millis(ms) "
|
// "var tend = self.millis(ms) "
|
||||||
"while !self.timereached(tend) "
|
// "while !self.time_reached(tend) "
|
||||||
"self.yield() "
|
// "self.yield() "
|
||||||
"end "
|
// "end "
|
||||||
"end "
|
// "end "
|
||||||
|
|
||||||
// Add command to list
|
// Add command to list
|
||||||
"def addcommand(c,f) "
|
"def add_cmd(c,f) "
|
||||||
|
"if !self._cmd "
|
||||||
|
"self._cmd={} "
|
||||||
|
"end "
|
||||||
"self._cmd[c]=f "
|
"self._cmd[c]=f "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
"def exec_cmd(cmd, idx, payload) "
|
"def exec_cmd(cmd, idx, payload) "
|
||||||
"import json "
|
"if self._cmd "
|
||||||
"var payload_json = json.load(payload) "
|
"import json "
|
||||||
"var cmd_found = self.findkeyi(self._cmd, cmd) "
|
"var payload_json = json.load(payload) "
|
||||||
"if cmd_found != nil "
|
"var cmd_found = self.find_key_i(self._cmd, cmd) "
|
||||||
"self.resolvecmnd(cmd_found) " // set the command name in XdrvMailbox.command
|
"if cmd_found != nil "
|
||||||
"self._cmd[cmd_found](cmd_found, idx, payload, payload_json) "
|
"self.resolvecmnd(cmd_found) " // set the command name in XdrvMailbox.command
|
||||||
"return true "
|
"self._cmd[cmd_found](cmd_found, idx, payload, payload_json) "
|
||||||
|
"return true "
|
||||||
|
"end "
|
||||||
"end "
|
"end "
|
||||||
"return false "
|
"return false "
|
||||||
"end "
|
"end "
|
||||||
|
@ -198,18 +214,62 @@ const char berry_prog[] =
|
||||||
"return gc.allocated() "
|
"return gc.allocated() "
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
|
//
|
||||||
|
// Event from Tasmota is:
|
||||||
|
// 1. event:string -- type of event (cmd, rule, ...)
|
||||||
|
// 2. cmd:string -- name of the command to process
|
||||||
|
// 3. index:int -- index number
|
||||||
|
// 4. payload:string -- payload as text, analyzed as json
|
||||||
|
//
|
||||||
|
"def event(type, cmd, idx, payload) "
|
||||||
|
"if type=='cmd' return self.exec_cmd(cmd, idx, payload) "
|
||||||
|
"elif type=='rule' return self.exec_rules(payload) "
|
||||||
|
"elif type=='mqtt_data' return nil " // not yet implemented
|
||||||
|
"elif type=='gc' return self.gc() "
|
||||||
|
"elif type=='every_50ms' return self.run_deferred() "
|
||||||
|
"elif self._drivers "
|
||||||
|
"for d:self._drivers "
|
||||||
|
"try "
|
||||||
|
"if d.dispatch && d.dispatch(type, cmd, idx, payload) nil " // nil for `nop`
|
||||||
|
"elif type=='every_second' && d.every_second return d.every_second() "
|
||||||
|
"elif type=='every_100ms' && d.every_100ms return d.every_100ms() "
|
||||||
|
"end "
|
||||||
|
"except .. as e,m "
|
||||||
|
"import string "
|
||||||
|
"log(string.format('BRY: exception %s - %m',3)) "
|
||||||
|
"end "
|
||||||
|
"end "
|
||||||
|
"end "
|
||||||
|
"end "
|
||||||
|
//
|
||||||
|
// add driver to the queue of event dispatching
|
||||||
|
//
|
||||||
|
"def add_driver(d) "
|
||||||
|
"if self._drivers "
|
||||||
|
"self._drivers.push(d) "
|
||||||
|
"else "
|
||||||
|
"self._drivers = [d]"
|
||||||
|
"end "
|
||||||
|
"end "
|
||||||
|
|
||||||
"end "
|
"end "
|
||||||
|
|
||||||
// Instantiate tasmota object
|
// Instantiate tasmota object
|
||||||
"tasmota = Tasmota() "
|
"tasmota = Tasmota() "
|
||||||
"wire = Wire(0) "
|
"wire = Wire(0) "
|
||||||
"wire1 = Wire(1) "
|
"wire1 = wire "
|
||||||
|
"wire2 = Wire(1) "
|
||||||
|
|
||||||
// Not sure how to run call methods from C
|
//
|
||||||
"def _exec_rules(e) return tasmota.exec_rules(e) end "
|
// Base class for Tasmota Driver
|
||||||
"def _run_deferred() return tasmota.run_deferred() end "
|
//
|
||||||
"def _exec_cmd(cmd, idx, payload) return tasmota.exec_cmd(cmd, idx, payload) end "
|
"class Driver "
|
||||||
"def _gc() return tasmota.gc() end "
|
// functions are needs to be set to trigger an event
|
||||||
|
"var dispatch " // general dispatcher, returns true if serviced
|
||||||
|
"var every_second " // called every_second
|
||||||
|
"var every_100ms " // called every 100ms
|
||||||
|
// ...
|
||||||
|
"end "
|
||||||
|
|
||||||
// simple wrapper to load a file
|
// simple wrapper to load a file
|
||||||
// prefixes '/' if needed, and simpler to use than `compile()`
|
// prefixes '/' if needed, and simpler to use than `compile()`
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
|
|
||||||
#include <berry.h>
|
#include <berry.h>
|
||||||
|
|
||||||
|
#define BERRY_CONSOLE_CMD_DELIMITER "\x01"
|
||||||
|
|
||||||
const char kBrCommands[] PROGMEM = D_PRFX_BR "|" // prefix
|
const char kBrCommands[] PROGMEM = D_PRFX_BR "|" // prefix
|
||||||
D_CMND_BR_RUN "|" D_CMND_BR_RESET
|
D_CMND_BR_RUN "|" D_CMND_BR_RESET
|
||||||
;
|
;
|
||||||
|
@ -32,14 +34,6 @@ void (* const BerryCommand[])(void) PROGMEM = {
|
||||||
CmndBrRun, CmndBrReset,
|
CmndBrRun, CmndBrReset,
|
||||||
};
|
};
|
||||||
|
|
||||||
class BerrySupport {
|
|
||||||
public:
|
|
||||||
bvm *vm = nullptr; // berry vm
|
|
||||||
bool rules_busy = false; // are we already processing rules, avoid infinite loop
|
|
||||||
bool autoexec_done = false; // do we still need to load 'autoexec.be'
|
|
||||||
};
|
|
||||||
BerrySupport berry;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Sanity Check for be_top()
|
// Sanity Check for be_top()
|
||||||
//
|
//
|
||||||
|
@ -64,79 +58,13 @@ bool callBerryRule(void) {
|
||||||
berry.rules_busy = true;
|
berry.rules_busy = true;
|
||||||
char * json_event = TasmotaGlobal.mqtt_data;
|
char * json_event = TasmotaGlobal.mqtt_data;
|
||||||
bool serviced = false;
|
bool serviced = false;
|
||||||
|
serviced = callBerryEventDispatcher(PSTR("exec_rules"), nullptr, 0, TasmotaGlobal.mqtt_data);
|
||||||
checkBeTop();
|
|
||||||
be_getglobal(berry.vm, "_exec_rules");
|
|
||||||
if (!be_isnil(berry.vm, -1)) {
|
|
||||||
|
|
||||||
// {
|
|
||||||
// String event_saved = TasmotaGlobal.mqtt_data;
|
|
||||||
// // json_event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
|
|
||||||
// // json_event = {"System":{"Boot":1}}
|
|
||||||
// // json_event = {"SerialReceived":"on"} - invalid but will be expanded to {"SerialReceived":{"Data":"on"}}
|
|
||||||
// char *p = strchr(json_event, ':');
|
|
||||||
// if ((p != NULL) && !(strchr(++p, ':'))) { // Find second colon
|
|
||||||
// event_saved.replace(F(":"), F(":{\"Data\":"));
|
|
||||||
// event_saved += F("}");
|
|
||||||
// // event_saved = {"SerialReceived":{"Data":"on"}}
|
|
||||||
// }
|
|
||||||
// be_pushstring(berry.vm, event_saved.c_str());
|
|
||||||
// }
|
|
||||||
be_pushstring(berry.vm, TasmotaGlobal.mqtt_data);
|
|
||||||
int ret = be_pcall(berry.vm, 1);
|
|
||||||
serviced = be_tobool(berry.vm, 1);
|
|
||||||
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Event (%s) serviced=%d"), TasmotaGlobal.mqtt_data, serviced);
|
|
||||||
be_pop(berry.vm, 2); // remove function object
|
|
||||||
} else {
|
|
||||||
be_pop(berry.vm, 1); // remove nil object
|
|
||||||
}
|
|
||||||
checkBeTop();
|
|
||||||
berry.rules_busy = false;
|
berry.rules_busy = false;
|
||||||
|
|
||||||
return serviced; // TODO event not handled
|
|
||||||
}
|
|
||||||
|
|
||||||
bool callBerryCommand(void) {
|
|
||||||
bool serviced = false;
|
|
||||||
|
|
||||||
checkBeTop();
|
|
||||||
be_getglobal(berry.vm, "_exec_cmd");
|
|
||||||
if (!be_isnil(berry.vm, -1)) {
|
|
||||||
be_pushstring(berry.vm, XdrvMailbox.topic);
|
|
||||||
be_pushint(berry.vm, XdrvMailbox.index);
|
|
||||||
be_pushstring(berry.vm, XdrvMailbox.data);
|
|
||||||
int ret = be_pcall(berry.vm, 3);
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: top=%d", be_top(berry.vm));
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(1)=%s", be_typename(berry.vm, 1));
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(2)=%s", be_typename(berry.vm, 2));
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(3)=%s", be_typename(berry.vm, 3));
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(4)=%s", be_typename(berry.vm, 4));
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(5)=%s", be_typename(berry.vm, 5));
|
|
||||||
serviced = be_tobool(berry.vm, 1); // return value is in slot 1
|
|
||||||
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: serviced=%d", serviced);
|
|
||||||
be_pop(berry.vm, 4); // remove function object
|
|
||||||
} else {
|
|
||||||
be_pop(berry.vm, 1); // remove nil object
|
|
||||||
}
|
|
||||||
checkBeTop();
|
|
||||||
|
|
||||||
return serviced; // TODO event not handled
|
return serviced; // TODO event not handled
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t callBerryGC(void) {
|
size_t callBerryGC(void) {
|
||||||
size_t ram_used = 0;
|
return callBerryEventDispatcher(PSTR("gc"), nullptr, 0, nullptr);
|
||||||
checkBeTop();
|
|
||||||
be_getglobal(berry.vm, "_gc");
|
|
||||||
if (!be_isnil(berry.vm, -1)) {
|
|
||||||
int ret = be_pcall(berry.vm, 0);
|
|
||||||
ram_used = be_toint(berry.vm, 1);
|
|
||||||
be_pop(berry.vm, 1); // remove function object
|
|
||||||
} else {
|
|
||||||
be_pop(berry.vm, 1); // remove nil object
|
|
||||||
}
|
|
||||||
checkBeTop();
|
|
||||||
|
|
||||||
return ram_used;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// void callBerryMqttData(void) {
|
// void callBerryMqttData(void) {
|
||||||
|
@ -161,16 +89,79 @@ size_t callBerryGC(void) {
|
||||||
// checkBeTop();
|
// checkBeTop();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// call a function (if exists) of type void -> void
|
/*
|
||||||
void callBerryFunctionVoid(const char * fname) {
|
// Call a method of a global object, with n args
|
||||||
if (nullptr == berry.vm) { return; }
|
// Before: stack must containt n args
|
||||||
checkBeTop();
|
// After: stack contains return value or nil if something wrong (args removes)
|
||||||
be_getglobal(berry.vm, fname);
|
// returns true is successful, false if object or method not found
|
||||||
|
bool callMethodObjectWithArgs(const char * objname, const char * method, size_t argc) {
|
||||||
|
if (nullptr == berry.vm) { return false; }
|
||||||
|
int32_t top = be_top(berry.vm);
|
||||||
|
// stacks contains n x arg
|
||||||
|
be_getglobal(berry.vm, objname);
|
||||||
|
// stacks contains n x arg + object
|
||||||
if (!be_isnil(berry.vm, -1)) {
|
if (!be_isnil(berry.vm, -1)) {
|
||||||
be_pcall(berry.vm, 0);
|
be_getmethod(berry.vm, -1, method);
|
||||||
|
// stacks contains n x arg + object + method
|
||||||
|
if (!be_isnil(berry.vm, -1)) {
|
||||||
|
// reshuffle the entire stack since we want: method + object + n x arg
|
||||||
|
be_pushvalue(berry.vm, -1); // add instance as first arg
|
||||||
|
// stacks contains n x arg + object + method + method
|
||||||
|
be_pushvalue(berry.vm, -3); // add instance as first arg
|
||||||
|
// stacks contains n x arg + object + method + method + object
|
||||||
|
// now move args 2 slots up to make room for method and object
|
||||||
|
for (uint32_t i = 1; i <= argc; i++) {
|
||||||
|
be_moveto(berry.vm, -4 - i, -2 - i);
|
||||||
|
}
|
||||||
|
// stacks contains free + free + n x arg + method + object
|
||||||
|
be_moveto(berry.vm, -2, -4 - argc);
|
||||||
|
be_moveto(berry.vm, -1, -3 - argc);
|
||||||
|
// stacks contains method + object + n x arg + method + object
|
||||||
|
be_pop(berry.vm, 2);
|
||||||
|
// stacks contains method + object + n x arg
|
||||||
|
be_pcall(berry.vm, argc + 1);
|
||||||
|
// stacks contains return_val + object + n x arg
|
||||||
|
be_pop(berry.vm, argc + 1);
|
||||||
|
// stacks contains return_val
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
be_pop(berry.vm, 1); // remove method
|
||||||
|
// stacks contains n x arg + object
|
||||||
}
|
}
|
||||||
be_pop(berry.vm, 1); // remove function or nil object
|
// stacks contains n x arg + object
|
||||||
|
be_pop(berry.vm, argc + 1); // clear stack
|
||||||
|
be_pushnil(berry.vm); // put nil object
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// call the event dispatcher from Tasmota object
|
||||||
|
int32_t callBerryEventDispatcher(const char *type, const char *cmd, int32_t idx, const char *payload) {
|
||||||
|
int32_t ret = 0;
|
||||||
|
|
||||||
|
if (nullptr == berry.vm) { return ret; }
|
||||||
checkBeTop();
|
checkBeTop();
|
||||||
|
be_getglobal(berry.vm, PSTR("tasmota"));
|
||||||
|
if (!be_isnil(berry.vm, -1)) {
|
||||||
|
be_getmethod(berry.vm, -1, PSTR("event"));
|
||||||
|
if (!be_isnil(berry.vm, -1)) {
|
||||||
|
be_pushvalue(berry.vm, -2); // add instance as first arg
|
||||||
|
be_pushstring(berry.vm, type != nullptr ? type : "");
|
||||||
|
be_pushstring(berry.vm, cmd != nullptr ? cmd : "");
|
||||||
|
be_pushint(berry.vm, idx);
|
||||||
|
be_pushstring(berry.vm, payload != nullptr ? payload : "{}"); // empty json
|
||||||
|
be_pcall(berry.vm, 5); // 5 arguments
|
||||||
|
be_pop(berry.vm, 5);
|
||||||
|
if (be_isint(berry.vm, -1)) {
|
||||||
|
ret = be_toint(berry.vm, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
be_pop(berry.vm, 1); // remove method
|
||||||
|
}
|
||||||
|
be_pop(berry.vm, 1); // remove instance object
|
||||||
|
checkBeTop();
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
|
@ -349,6 +340,313 @@ void CmndBrReset(void) {
|
||||||
BrReset();
|
BrReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Berry console
|
||||||
|
\*********************************************************************************************/
|
||||||
|
#ifdef USE_WEBSERVER
|
||||||
|
|
||||||
|
void BrREPLRun(char * cmd) {
|
||||||
|
if (berry.vm == nullptr) { return; }
|
||||||
|
|
||||||
|
size_t cmd_len = strlen(cmd);
|
||||||
|
size_t cmd2_len = cmd_len + 12;
|
||||||
|
char * cmd2 = (char*) malloc(cmd2_len);
|
||||||
|
do {
|
||||||
|
int32_t ret_code;
|
||||||
|
|
||||||
|
snprintf_P(cmd2, cmd2_len, PSTR("return (%s)"), cmd);
|
||||||
|
ret_code = be_loadbuffer(berry.vm, PSTR("input"), cmd2, strlen(cmd2));
|
||||||
|
// AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_loadbuffer cmd2 '%s', ret=%i"), cmd2, ret_code);
|
||||||
|
if (be_getexcept(berry.vm, ret_code) == BE_SYNTAX_ERROR) {
|
||||||
|
be_pop(berry.vm, 2); // remove exception values
|
||||||
|
// if fails, try the direct command
|
||||||
|
ret_code = be_loadbuffer(berry.vm, PSTR("input"), cmd, cmd_len);
|
||||||
|
// AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_loadbuffer cmd1 '%s', ret=%i"), cmd, ret_code);
|
||||||
|
}
|
||||||
|
if (0 == ret_code) { // code is ready to run
|
||||||
|
ret_code = be_pcall(berry.vm, 0); // execute code
|
||||||
|
// AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_pcall ret=%i"), ret_code);
|
||||||
|
if (0 == ret_code) {
|
||||||
|
if (!be_isnil(berry.vm, 1)) {
|
||||||
|
const char * ret_val = be_tostring(berry.vm, 1);
|
||||||
|
berry.log.addString(ret_val, nullptr, "\n");
|
||||||
|
// AddLog_P(LOG_LEVEL_INFO, PSTR(">>> %s"), ret_val);
|
||||||
|
}
|
||||||
|
be_pop(berry.vm, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (BE_EXCEPTION == ret_code) {
|
||||||
|
be_dumpstack(berry.vm);
|
||||||
|
char exception_s[120];
|
||||||
|
ext_snprintf_P(exception_s, sizeof(exception_s), PSTR("%s: %s"), be_tostring(berry.vm, -2), be_tostring(berry.vm, -1));
|
||||||
|
berry.log.addString(exception_s, nullptr, "\n");
|
||||||
|
// AddLog_P(LOG_LEVEL_INFO, PSTR(">>> %s"), exception_s);
|
||||||
|
be_pop(berry.vm, 2);
|
||||||
|
}
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
if (cmd2 != nullptr) {
|
||||||
|
free(cmd2);
|
||||||
|
cmd2 = nullptr;
|
||||||
|
}
|
||||||
|
checkBeTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char HTTP_SCRIPT_BERRY_CONSOLE[] PROGMEM =
|
||||||
|
"var sn=0,id=0,ft,ltm=%d;" // Scroll position, Get most of weblog initially
|
||||||
|
// Console command history
|
||||||
|
"var hc=[],cn=0;" // hc = History commands, cn = Number of history being shown
|
||||||
|
|
||||||
|
"function l(p){" // Console log and command service
|
||||||
|
"var c,cc,o='';"
|
||||||
|
"clearTimeout(lt);"
|
||||||
|
"clearTimeout(ft);"
|
||||||
|
"t=eb('t1');"
|
||||||
|
"if(p==1){"
|
||||||
|
"c=eb('c1');" // Console command id
|
||||||
|
"cc=c.value.trim();"
|
||||||
|
"if(cc){"
|
||||||
|
"o='&c1='+encodeURIComponent(cc);"
|
||||||
|
"hc.length>19&&hc.pop();"
|
||||||
|
"hc.unshift(cc);"
|
||||||
|
"cn=0;"
|
||||||
|
"}"
|
||||||
|
"c.value='';"
|
||||||
|
"t.scrollTop=99999;"
|
||||||
|
"sn=t.scrollTop;"
|
||||||
|
"}"
|
||||||
|
"if(t.scrollTop>=sn){" // User scrolled back so no updates
|
||||||
|
"if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1)
|
||||||
|
"x=new XMLHttpRequest();"
|
||||||
|
"x.onreadystatechange=function(){"
|
||||||
|
"if(x.readyState==4&&x.status==200){"
|
||||||
|
"var d,t1;"
|
||||||
|
"d=x.responseText.split(/" BERRY_CONSOLE_CMD_DELIMITER "/);" // Field separator
|
||||||
|
"var d1=d.shift();"
|
||||||
|
"if(d1){"
|
||||||
|
"t1=document.createElement('div');"
|
||||||
|
"t1.classList.add('br1');"
|
||||||
|
"t1.innerText=d1;"
|
||||||
|
"t.appendChild(t1);"
|
||||||
|
"}"
|
||||||
|
"d1=d.shift();"
|
||||||
|
"if(d1){"
|
||||||
|
"t1=document.createElement('div');"
|
||||||
|
"t1.classList.add('br2');"
|
||||||
|
"t1.innerText=d1;"
|
||||||
|
"t.appendChild(t1);"
|
||||||
|
"}"
|
||||||
|
"t.scrollTop=99999;"
|
||||||
|
"sn=t.scrollTop;"
|
||||||
|
"clearTimeout(ft);"
|
||||||
|
"lt=setTimeout(l,ltm);" // webrefresh timer....
|
||||||
|
"}"
|
||||||
|
"};"
|
||||||
|
"x.open('GET','bs?c2='+id+o,true);" // Related to Webserver->hasArg("c2") and WebGetArg("c2", stmp, sizeof(stmp))
|
||||||
|
"x.send();"
|
||||||
|
"ft=setTimeout(l,20000);" // fail timeout, triggered 20s after asking for XHR
|
||||||
|
"}else{"
|
||||||
|
"lt=setTimeout(l,ltm);" // webrefresh timer....
|
||||||
|
"}"
|
||||||
|
"c1.focus();"
|
||||||
|
"return false;"
|
||||||
|
"}"
|
||||||
|
"wl(l);" // Load initial console text
|
||||||
|
; // Add console command key eventlistener after name has been synced with id (= wl(jd))
|
||||||
|
|
||||||
|
const char HTTP_SCRIPT_BERRY_CONSOLE2[] PROGMEM =
|
||||||
|
// // Console command history
|
||||||
|
// "var hc=[],cn=0;" // hc = History commands, cn = Number of history being shown
|
||||||
|
"var pc=0;" // pc = previous char
|
||||||
|
"function h(){"
|
||||||
|
// "if(!(navigator.maxTouchPoints||'ontouchstart'in document.documentElement)){eb('c1').autocomplete='off';}" // No touch so stop browser autocomplete
|
||||||
|
"eb('c1').addEventListener('keydown',function(e){"
|
||||||
|
"var b=eb('c1'),c=e.keyCode;" // c1 = Console command id
|
||||||
|
"if((38==c||40==c)&&0==this.selectionStart&&0==this.selectionEnd){"
|
||||||
|
"b.autocomplete='off';"
|
||||||
|
"e.preventDefault();"
|
||||||
|
"38==c?(++cn>hc.length&&(cn=hc.length),b.value=hc[cn-1]||''):" // ArrowUp
|
||||||
|
"40==c?(0>--cn&&(cn=0),b.value=hc[cn-1]||''):" // ArrowDown
|
||||||
|
"0;"
|
||||||
|
"this.selectionStart=this.selectionEnd=0;"
|
||||||
|
"}" // ArrowUp or ArrowDown must be a keyboard so stop browser autocomplete
|
||||||
|
"if(c==13&&pc==13){"
|
||||||
|
"e.preventDefault();" // prevent 'enter' from being inserted
|
||||||
|
"l(1);"
|
||||||
|
"}"
|
||||||
|
"if(c==9){"
|
||||||
|
"e.preventDefault();"
|
||||||
|
"var start=this.selectionStart;"
|
||||||
|
"var end=this.selectionEnd;"
|
||||||
|
// set textarea value to: text before caret + tab + text after caret
|
||||||
|
"this.value=this.value.substring(0, start)+\" \"+this.value.substring(end);"
|
||||||
|
// put caret at right position again
|
||||||
|
"this.selectionStart=this.selectionEnd=start + 1;"
|
||||||
|
"}"
|
||||||
|
"pc=c;" // record previous key
|
||||||
|
// "13==c&&(hc.length>19&&hc.pop(),hc.unshift(b.value),cn=0)" // Enter, 19 = Max number -1 of commands in history
|
||||||
|
"});"
|
||||||
|
"}"
|
||||||
|
"wl(h);"; // Add console command key eventlistener after name has been synced with id (= wl(jd))
|
||||||
|
|
||||||
|
const char HTTP_BERRY_STYLE_CMND[] PROGMEM =
|
||||||
|
"<style>"
|
||||||
|
".br1{" // berry output
|
||||||
|
"border-left:dotted 2px #860;"
|
||||||
|
"margin-bottom:4px;"
|
||||||
|
"margin-top:4px;"
|
||||||
|
"padding:1px 5px 1px 18px;"
|
||||||
|
"}"
|
||||||
|
".br2{" // user input
|
||||||
|
"padding:0px 5px 0px 5px;"
|
||||||
|
"color:#faffff;"
|
||||||
|
"}"
|
||||||
|
".br0{"
|
||||||
|
// "-moz-appearance: textfield-multiline;"
|
||||||
|
// "-webkit-appearance: textarea;"
|
||||||
|
"font:medium -moz-fixed;"
|
||||||
|
"font:-webkit-small-control;"
|
||||||
|
"box-sizing:border-box;"
|
||||||
|
"width:100%;"
|
||||||
|
"overflow:auto;"
|
||||||
|
"resize:vertical;"
|
||||||
|
"font-family:monospace;"
|
||||||
|
"overflow:auto;"
|
||||||
|
"font-size:1em;"
|
||||||
|
"}"
|
||||||
|
".bro{"
|
||||||
|
// "-moz-appearance: textfield-multiline;"
|
||||||
|
// "-webkit-appearance: textarea;"
|
||||||
|
"border:1px solid gray;"
|
||||||
|
"height:250px;"
|
||||||
|
"padding:2px;"
|
||||||
|
"background:#222;"
|
||||||
|
"color:#fb1;"
|
||||||
|
"white-space:pre;"
|
||||||
|
"padding:2px 5px 2px 5px;"
|
||||||
|
"}"
|
||||||
|
".bri{"
|
||||||
|
// "-moz-appearance: textfield-multiline;"
|
||||||
|
// "-webkit-appearance: textarea;"
|
||||||
|
"border:1px solid gray;"
|
||||||
|
"height:60px;"
|
||||||
|
"padding:5px;"
|
||||||
|
"color:#000000;background:#faffff"
|
||||||
|
"}"
|
||||||
|
"</style>"
|
||||||
|
;
|
||||||
|
|
||||||
|
const char HTTP_BERRY_FORM_CMND[] PROGMEM =
|
||||||
|
"<br>"
|
||||||
|
"<div contenteditable='false' class='br0 bro' readonly id='t1' cols='340' wrap='off'>"
|
||||||
|
"<div class='br1'>Welcome to the Berry Scripting console. "
|
||||||
|
"Check the <a href='https://tasmota.github.io/docs/Berry-Scripting/' target='_blank'>documentation</a>."
|
||||||
|
"</div>"
|
||||||
|
"</div>"
|
||||||
|
// "<textarea readonly id='t1' cols='340' wrap='off'></textarea>"
|
||||||
|
// "<br><br>"
|
||||||
|
"<form method='get' id='fo' onsubmit='return l(1);'>"
|
||||||
|
"<textarea id='c1' class='br0 bri' rows='4' cols='340' wrap='soft' autofocus required></textarea>"
|
||||||
|
// "<input id='c1' class='bri' type='text' rows='5' placeholder='" D_ENTER_COMMAND "' autofocus><br>"
|
||||||
|
// "<input type='submit' value=\"Run code (or press 'Enter' twice)\">"
|
||||||
|
"<button type='submit'>Run code (or press 'Enter' twice)</button>"
|
||||||
|
"</form>";
|
||||||
|
|
||||||
|
const char HTTP_BTN_BERRY_CONSOLE[] PROGMEM =
|
||||||
|
"<p><form action='bs' method='get'><button>Berry Scripting console</button></form></p>";
|
||||||
|
|
||||||
|
|
||||||
|
void HandleBerryConsoleRefresh(void)
|
||||||
|
{
|
||||||
|
String svalue = Webserver->arg(F("c1"));
|
||||||
|
|
||||||
|
svalue.trim();
|
||||||
|
if (svalue.length()) {
|
||||||
|
berry.log.reset(); // clear all previous logs
|
||||||
|
berry.repl_active = true; // start recording
|
||||||
|
AddLog_P(LOG_LEVEL_INFO, PSTR("BRY: received command %s"), svalue.c_str());
|
||||||
|
berry.log.addString(svalue.c_str(), nullptr, BERRY_CONSOLE_CMD_DELIMITER);
|
||||||
|
|
||||||
|
// Call berry
|
||||||
|
BrREPLRun((char*)svalue.c_str());
|
||||||
|
berry.repl_active = false; // don't record further
|
||||||
|
}
|
||||||
|
|
||||||
|
WSContentBegin(200, CT_PLAIN);
|
||||||
|
|
||||||
|
if (!berry.log.isEmpty()) {
|
||||||
|
|
||||||
|
WSContentFlush();
|
||||||
|
|
||||||
|
for (auto & l: berry.log.log) {
|
||||||
|
_WSContentSend((char*) l);
|
||||||
|
}
|
||||||
|
|
||||||
|
berry.log.reset();
|
||||||
|
}
|
||||||
|
WSContentEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleBerryConsole(void)
|
||||||
|
{
|
||||||
|
if (!HttpCheckPriviledgedAccess()) { return; }
|
||||||
|
// int i=16;
|
||||||
|
// // AddLog(LOG_LEVEL_INFO, PSTR("Size = %d %d"), sizeof(LList_elt<char[12]>), sizeof(LList_elt<char[0]>)+12);
|
||||||
|
// LList_elt<char[0]> * elt = (LList_elt<char[0]>*) ::operator new(sizeof(LList_elt<char[0]>) + 12);
|
||||||
|
|
||||||
|
if (Webserver->hasArg(F("c2"))) { // Console refresh requested
|
||||||
|
HandleBerryConsoleRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP "Berry " D_CONSOLE));
|
||||||
|
|
||||||
|
WSContentStart_P(PSTR("Berry " D_CONSOLE));
|
||||||
|
WSContentSend_P(HTTP_SCRIPT_BERRY_CONSOLE, Settings.web_refresh);
|
||||||
|
WSContentSend_P(HTTP_SCRIPT_BERRY_CONSOLE2);
|
||||||
|
WSContentSendStyle();
|
||||||
|
WSContentFlush();
|
||||||
|
_WSContentSend(HTTP_BERRY_STYLE_CMND);
|
||||||
|
_WSContentSend(HTTP_BERRY_FORM_CMND);
|
||||||
|
WSContentSpaceButton(BUTTON_MAIN);
|
||||||
|
WSContentStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// void HandleBerryConsoleRefresh(void)
|
||||||
|
// {
|
||||||
|
// String svalue = Webserver->arg(F("c1"));
|
||||||
|
// if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
|
||||||
|
// // TODO run command and store result
|
||||||
|
// // AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str());
|
||||||
|
// // ExecuteWebCommand((char*)svalue.c_str(), SRC_WEBCONSOLE);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// char stmp[8];
|
||||||
|
// WebGetArg(PSTR("c2"), stmp, sizeof(stmp));
|
||||||
|
// uint32_t index = 0; // Initial start, dump all
|
||||||
|
// if (strlen(stmp)) { index = atoi(stmp); }
|
||||||
|
|
||||||
|
// WSContentBegin(200, CT_PLAIN);
|
||||||
|
// WSContentSend_P(PSTR("%d}1%d}1"), TasmotaGlobal.log_buffer_pointer, Web.reset_web_log_flag);
|
||||||
|
// if (!Web.reset_web_log_flag) {
|
||||||
|
// index = 0;
|
||||||
|
// Web.reset_web_log_flag = true;
|
||||||
|
// }
|
||||||
|
// bool cflg = (index);
|
||||||
|
// char* line;
|
||||||
|
// size_t len;
|
||||||
|
// while (GetLog(Settings.weblog_level, &index, &line, &len)) {
|
||||||
|
// if (len > sizeof(TasmotaGlobal.mqtt_data) -2) { len = sizeof(TasmotaGlobal.mqtt_data); }
|
||||||
|
// char stemp[len +1];
|
||||||
|
// strlcpy(stemp, line, len);
|
||||||
|
// WSContentSend_P(PSTR("%s%s"), (cflg) ? PSTR("\n") : "", stemp);
|
||||||
|
// cflg = true;
|
||||||
|
// }
|
||||||
|
// WSContentSend_P(PSTR("}1"));
|
||||||
|
// WSContentEnd();
|
||||||
|
// }
|
||||||
|
#endif // USE_WEBSERVER
|
||||||
|
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* Interface
|
* Interface
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
@ -363,51 +661,63 @@ bool Xdrv52(uint8_t function)
|
||||||
break;
|
break;
|
||||||
case FUNC_LOOP:
|
case FUNC_LOOP:
|
||||||
if (!berry.autoexec_done) {
|
if (!berry.autoexec_done) {
|
||||||
BrAutoexec();
|
BrAutoexec(); // run autoexec.be at first tick, so we know all modules are initialized
|
||||||
berry.autoexec_done = true;
|
berry.autoexec_done = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Berry wide commands and events
|
||||||
|
case FUNC_RULES_PROCESS:
|
||||||
|
result = callBerryRule();
|
||||||
|
break;
|
||||||
|
case FUNC_MQTT_DATA:
|
||||||
|
result = callBerryEventDispatcher(PSTR("mqtt_data"), XdrvMailbox.topic, 0, XdrvMailbox.data);
|
||||||
|
break;
|
||||||
case FUNC_EVERY_50_MSECOND:
|
case FUNC_EVERY_50_MSECOND:
|
||||||
callBerryFunctionVoid(PSTR("_run_deferred"));
|
callBerryEventDispatcher(PSTR("every_50ms"), nullptr, 0, nullptr);
|
||||||
break;
|
|
||||||
case FUNC_EVERY_100_MSECOND:
|
|
||||||
callBerryFunctionVoid(PSTR("every_100ms"));
|
|
||||||
break;
|
|
||||||
case FUNC_EVERY_SECOND:
|
|
||||||
callBerryFunctionVoid(PSTR("every_second"));
|
|
||||||
break;
|
break;
|
||||||
case FUNC_COMMAND:
|
case FUNC_COMMAND:
|
||||||
result = DecodeCommand(kBrCommands, BerryCommand);
|
result = DecodeCommand(kBrCommands, BerryCommand);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = callBerryCommand();
|
result = callBerryEventDispatcher(PSTR("cmd"), XdrvMailbox.topic, XdrvMailbox.index, XdrvMailbox.data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Module specific events
|
||||||
|
case FUNC_EVERY_100_MSECOND:
|
||||||
|
callBerryEventDispatcher(PSTR("every_100ms"), nullptr, 0, nullptr);
|
||||||
|
break;
|
||||||
|
case FUNC_EVERY_SECOND:
|
||||||
|
callBerryEventDispatcher(PSTR("every_second"), nullptr, 0, nullptr);
|
||||||
|
break;
|
||||||
// case FUNC_SET_POWER:
|
// case FUNC_SET_POWER:
|
||||||
// break;
|
// break;
|
||||||
case FUNC_RULES_PROCESS:
|
|
||||||
result = callBerryRule();
|
|
||||||
break;
|
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
case FUNC_WEB_ADD_BUTTON:
|
case FUNC_WEB_ADD_BUTTON:
|
||||||
|
WSContentSend_P(HTTP_BTN_BERRY_CONSOLE);
|
||||||
|
callBerryEventDispatcher(PSTR("web_add_button"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
case FUNC_WEB_ADD_MAIN_BUTTON:
|
case FUNC_WEB_ADD_MAIN_BUTTON:
|
||||||
|
callBerryEventDispatcher(PSTR("web_add_main_button"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
case FUNC_WEB_ADD_HANDLER:
|
case FUNC_WEB_ADD_HANDLER:
|
||||||
|
callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr);
|
||||||
|
WebServer_on(PSTR("/bs"), HandleBerryConsole);
|
||||||
break;
|
break;
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
case FUNC_SAVE_BEFORE_RESTART:
|
case FUNC_SAVE_BEFORE_RESTART:
|
||||||
break;
|
callBerryEventDispatcher(PSTR("save_before_restart"), nullptr, 0, nullptr);
|
||||||
case FUNC_MQTT_DATA:
|
|
||||||
// callBerryMqttData();
|
|
||||||
break;
|
break;
|
||||||
case FUNC_WEB_SENSOR:
|
case FUNC_WEB_SENSOR:
|
||||||
|
callBerryEventDispatcher(PSTR("web_sensor"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FUNC_JSON_APPEND:
|
case FUNC_JSON_APPEND:
|
||||||
|
callBerryEventDispatcher(PSTR("json_aooend"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FUNC_BUTTON_PRESSED:
|
case FUNC_BUTTON_PRESSED:
|
||||||
|
callBerryEventDispatcher(PSTR("button_pressed"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue