Merge pull request #11399 from s-hadinger/berry_mar_20

Berry milestone March 20
This commit is contained in:
s-hadinger 2021-03-20 20:03:05 +01:00 committed by GitHub
commit d18604f25d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 757 additions and 409 deletions

View File

@ -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();
} }

View File

@ -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:

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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 -#

View File

@ -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
-#

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
} }

View File

@ -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
} }

View File

@ -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()`

View File

@ -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;