diff --git a/lib/lib_i2c/MPU6886/src/MPU6886.cpp b/lib/lib_i2c/MPU6886/src/MPU6886.cpp index 9ae45461d..8774d09f7 100755 --- a/lib/lib_i2c/MPU6886/src/MPU6886.cpp +++ b/lib/lib_i2c/MPU6886/src/MPU6886.cpp @@ -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){ - myWire.beginTransmission(driver_Addr); - myWire.write(start_Addr); - myWire.endTransmission(false); + myWire->beginTransmission(driver_Addr); + myWire->write(start_Addr); + myWire->endTransmission(false); uint8_t i = 0; - myWire.requestFrom(driver_Addr,number_Bytes); + myWire->requestFrom(driver_Addr,number_Bytes); //! Put read results in the Rx buffer - while (myWire.available()) { - read_Buffer[i++] = myWire.read(); + while (myWire->available()) { + 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){ - myWire.beginTransmission(driver_Addr); - myWire.write(start_Addr); - myWire.write(*write_Buffer); - myWire.endTransmission(); + myWire->beginTransmission(driver_Addr); + myWire->write(start_Addr); + myWire->write(*write_Buffer); + myWire->endTransmission(); } diff --git a/lib/lib_i2c/MPU6886/src/MPU6886.h b/lib/lib_i2c/MPU6886/src/MPU6886.h index 025f71587..b4a5541e0 100755 --- a/lib/lib_i2c/MPU6886/src/MPU6886.h +++ b/lib/lib_i2c/MPU6886/src/MPU6886.h @@ -71,9 +71,9 @@ class MPU6886 { public: MPU6886(void) {}; #ifdef ESP32 - void setBus(uint32_t _bus) { myWire = _bus ? Wire1 : Wire; }; + void setBus(uint32_t _bus) { myWire = _bus ? &Wire1 : &Wire; }; #else - void setBus(uint32_t _bus) { myWire = Wire; }; + void setBus(uint32_t _bus) { myWire = &Wire; }; #endif int Init(void); 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); public: - TwoWire & myWire = Wire; // default to Wire (bus 0) + TwoWire * myWire = &Wire; // default to Wire (bus 0) float aRes, gRes; private: diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c b/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c index 444133852..020766460 100644 --- a/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c +++ b/lib/libesp32/Berry-0.1.10/src/port/be_energylib.c @@ -19,7 +19,7 @@ be_define_native_module(energy, NULL); #else /* @const_object_info_begin module tasmota (scope: global, depend: 1) { - getfreeheap, func(l_getFreeHeap) + get_free_heap, func(l_getFreeHeap) } @const_object_info_end */ #include "../generate/be_fixed_tasmota.h" diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c b/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c index 17b09a219..713effefd 100644 --- a/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c +++ b/lib/libesp32/Berry-0.1.10/src/port/be_tasmotalib.c @@ -27,6 +27,8 @@ extern int l_getpower(bvm *vm); extern int l_setlight(bvm *vm); extern int l_setpower(bvm *vm); +extern int l_i2cenabled(bvm *vm); + // #if !BE_USE_PRECOMPILED_OBJECT #if 1 // TODO we will do pre-compiled later // Class definition @@ -39,27 +41,30 @@ void be_load_tasmota_ntvlib(bvm *vm) { "_rules", NULL }, { "_timers", NULL }, { "_cmd", NULL }, - { "getfreeheap", l_getFreeHeap }, + { "_drivers", NULL }, + { "get_free_heap", l_getFreeHeap }, { "publish", l_publish }, { "cmd", l_cmd }, - { "getoption", l_getoption }, + { "get_option", l_getoption }, { "millis", l_millis }, - { "timereached", l_timereached }, + { "time_reached", l_timereached }, { "yield", l_yield }, { "delay", l_delay }, - { "scaleuint", l_scaleuint }, + { "scale_uint", l_scaleuint }, - { "respcmnd", l_respCmnd }, - { "respcmndstr", l_respCmndStr }, - { "respcmnd_done", l_respCmndDone }, - { "respcmnd_error", l_respCmndError }, - { "respcmnd_failed", l_respCmndFailed }, + { "resp_cmnd", l_respCmnd }, + { "resp_cmnd_str", l_respCmndStr }, + { "resp_cmnd_done", l_respCmndDone }, + { "resp_cmnd_error", l_respCmndError }, + { "resp_cmnd_failed", l_respCmndFailed }, { "resolvecmnd", l_resolveCmnd }, - { "getlight", l_getlight }, - { "getpower", l_getpower }, - { "setlight", l_setlight }, - { "setpower", l_setpower }, + { "get_light", l_getlight }, + { "get_power", l_getpower }, + { "set_light", l_setlight }, + { "set_power", l_setpower }, + + { "i2c_enabled", l_i2cenabled }, { NULL, NULL } }; @@ -69,7 +74,7 @@ void be_load_tasmota_ntvlib(bvm *vm) #else /* @const_object_info_begin module tasmota (scope: global, depend: 1) { - getfreeheap, func(l_getFreeHeap) + get_free_heap, func(l_getFreeHeap) } @const_object_info_end */ #include "../generate/be_fixed_tasmota.h" diff --git a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c index 07581b2a4..b6a81c469 100644 --- a/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c +++ b/lib/libesp32/Berry-0.1.10/src/port/be_wirelib.c @@ -20,6 +20,9 @@ extern int b_wire_scan(bvm *vm); extern int b_wire_validwrite(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 1 // TODO we will do pre-compiled later @@ -28,15 +31,18 @@ void be_load_wirelib(bvm *vm) static const bnfuncinfo members[] = { { "_bus", NULL }, // bus number { "init", b_wire_init }, - { "_begintransmission", b_wire_begintransmission }, - { "_endtransmission", b_wire_endtransmission }, - { "_requestfrom", b_wire_requestfrom }, + { "_begin_transmission", b_wire_begintransmission }, + { "_end_transmission", b_wire_endtransmission }, + { "_request_from", b_wire_requestfrom }, { "_available", b_wire_available }, { "_write", b_wire_write }, { "_read", b_wire_read }, { "scan", b_wire_scan }, { "write", b_wire_validwrite }, { "read", b_wire_validread }, + { "read_bytes", b_wire_validread }, + { "write_bytes", b_wire_validread }, + { "detect", b_wire_detect }, { NULL, NULL } }; @@ -45,7 +51,7 @@ void be_load_wirelib(bvm *vm) #else /* @const_object_info_begin module tasmota (scope: global, depend: 1) { - getfreeheap, func(l_getFreeHeap) + get_free_heap, func(l_getFreeHeap) } @const_object_info_end */ #include "../generate/be_fixed_tasmota.h" diff --git a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h index e3720b4d1..57fbe6208 100644 --- a/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h +++ b/lib/libesp32/Berry-0.1.10/src/port/berry_conf.h @@ -162,11 +162,21 @@ * are not required. * 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_EXIT exit -#define BE_EXPLICIT_MALLOC malloc +// #define BE_EXPLICIT_MALLOC malloc #define BE_EXPLICIT_FREE free -#define BE_EXPLICIT_REALLOC realloc +// #define BE_EXPLICIT_REALLOC realloc /* Macro: be_assert * Berry debug assertion. Only enabled when BE_DEBUG is active. diff --git a/tasmota/berry/denky.be b/tasmota/berry/denky.be index bcec51407..416e26078 100644 --- a/tasmota/berry/denky.be +++ b/tasmota/berry/denky.be @@ -10,11 +10,11 @@ runcolor = nil def runcolor() var pwr = energy.read().find('activepower',0) 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 channels = [red, green, 0] - tasmota.setlight({"channels":channels, "bri":64, "power":true}) - tasmota.settimer(2000, runcolor) + tasmota.set_light({"channels":channels, "bri":64, "power":true}) + tasmota.set_timer(2000, runcolor) end #- run animation -# diff --git a/tasmota/berry/tasmota.be b/tasmota/berry/tasmota.be deleted file mode 100644 index 213eb0be0..000000000 --- a/tasmota/berry/tasmota.be +++ /dev/null @@ -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 - --# \ No newline at end of file diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 51c3999ac..f27f920d5 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -463,6 +463,11 @@ // #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 ---------------------------- #define ROTARY_V1 // Add support for Rotary Encoder as used in MI Desk Lamp (+0k8 code) #define ROTARY_MAX_STEPS 10 // Rotary step boundary diff --git a/tasmota/support_esp.ino b/tasmota/support_esp.ino index 1f929f400..65dd3871c 100644 --- a/tasmota/support_esp.ino +++ b/tasmota/support_esp.ino @@ -74,6 +74,10 @@ void *special_malloc(uint32_t size) { return malloc(size); } +void *special_realloc(void *ptr, size_t size) { + return realloc(ptr, size); +} + #endif /*********************************************************************************************\ @@ -461,6 +465,13 @@ void *special_malloc(uint32_t 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 diff --git a/tasmota/xdrv_52_0_berry_struct.ino b/tasmota/xdrv_52_0_berry_struct.ino new file mode 100644 index 000000000..22f062ba5 --- /dev/null +++ b/tasmota/xdrv_52_0_berry_struct.ino @@ -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 . +*/ + + +#ifdef USE_BERRY + +#include + +typedef LList_elt log_elt; // store the string after the header to avoid double allocation if we had used char* + +class BerryLog { +public: + // typedef LList_elt 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 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 diff --git a/tasmota/xdrv_52_3_berry_tasmota.ino b/tasmota/xdrv_52_3_berry_tasmota.ino index f39f3cc1c..bd9e1a8cc 100644 --- a/tasmota/xdrv_52_3_berry_tasmota.ino +++ b/tasmota/xdrv_52_3_berry_tasmota.ino @@ -23,6 +23,8 @@ #include #include +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 * @@ -30,18 +32,18 @@ * * import tasmota * - * tasmota.getfreeheap() -> int + * tasmota.get_free_heap() -> int * tasmota.publish(topic:string, payload:string[, retain:bool]) -> nil * tasmota.cmd(command:string) -> string - * tasmota.getoption(index:int) -> int + * tasmota.get_option(index:int) -> int * tasmota.millis([delay:int]) -> int - * tasmota.timereached(timer:int) -> bool + * tasmota.time_reached(timer:int) -> bool * tasmota.yield() -> nil * - * tasmota.getlight([index:int = 0]) -> map - * tasmota.getpower([index:int = 0]) -> bool - * tasmota.setpower(idx:int, power:bool) -> bool or nil - * tasmota.setlight(idx:int, values:map) -> map + * tasmota.get_light([index:int = 0]) -> map + * tasmota.get_power([index:int = 0]) -> bool + * tasmota.set_power(idx:int, power:bool) -> bool or nil + * tasmota.set_light(idx:int, values:map) -> map * \*********************************************************************************************/ extern "C" { @@ -97,7 +99,7 @@ extern "C" { 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) { @@ -110,7 +112,7 @@ extern "C" { 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) { @@ -145,7 +147,7 @@ extern "C" { 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) { @@ -470,6 +472,24 @@ extern "C" { 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()` 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); } diff --git a/tasmota/xdrv_52_3_berry_wire.ino b/tasmota/xdrv_52_3_berry_wire.ino index 5959b98b9..70e457ca9 100644 --- a/tasmota/xdrv_52_3_berry_wire.ino +++ b/tasmota/xdrv_52_3_berry_wire.ino @@ -48,7 +48,7 @@ int32_t getBus(bvm *vm) { * * import wire * - * wire.getfreeheap() -> int + * wire.get_free_heap() -> int * \*********************************************************************************************/ 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 top = be_top(vm); // Get the number of arguments + const void * buf; + size_t len; TwoWire & myWire = getWire(vm); if (top == 2 && (be_isint(vm, 2) || be_isstring(vm, 2))) { if (be_isint(vm, 2)) { @@ -143,6 +145,8 @@ extern "C" { } else if (be_isstring(vm, 2)) { const char * s = be_tostring(vm, 1); myWire.write(s); + } else if ((buf = be_tobytes(vm, 2, &len)) != nullptr) { + myWire.write((uint8_t*) buf, len); } else { be_return_nil(vm); } @@ -211,7 +215,7 @@ extern "C" { 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); // TODO + bool ok = I2cValidRead(addr, reg, size, bus); // TODO if (ok) { be_pushint(vm, i2c_buffer); } else { @@ -221,6 +225,62 @@ extern "C" { } 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 // 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_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_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 } diff --git a/tasmota/xdrv_52_7_berry_embedded.ino b/tasmota/xdrv_52_7_berry_embedded.ino index 83a3895e7..65dfb616f 100644 --- a/tasmota/xdrv_52_7_berry_embedded.ino +++ b/tasmota/xdrv_52_7_berry_embedded.ino @@ -55,14 +55,11 @@ const char berry_prog[] = "/f1,f2-> real(f1) < real(f2)," "] " "self._operators = \"=<>!|\" " - "self._rules = {} " - "self._timers = [] " - "self._cmd = {} " "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 // or -1 if not found - "def charsinstring(s,c) " + "def chars_in_string(s,c) " "for i:0..size(s)-1 " "for j:0..size(c)-1 " "if s[i] == c[j] return i end " @@ -72,7 +69,7 @@ const char berry_prog[] = "end " // 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 " "var keyu = string.toupper(keyi) " "if classof(m) == map " @@ -84,14 +81,11 @@ const char berry_prog[] = "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) // # ex: "Dimmer>50" -> ["Dimmer",tasmota_gt,"50"] "def find_op(item) " "import string " - "var pos = self.charsinstring(item, self._operators) " + "var pos = self.chars_in_string(item, self._operators) " "if pos>=0 " "var op_split = string.split(item,pos) " // #print(op_split) @@ -109,6 +103,14 @@ const char berry_prog[] = "end " "return [item, nil, nil] " "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 "def try_rule(ev, rule, f) " @@ -117,7 +119,7 @@ const char berry_prog[] = "var e=ev " "var rl=string.split(rl_list[0],'#') " "for it:rl " - "found=self.findkeyi(e,it) " + "found=self.find_key_i(e,it) " "if found == nil " "return false " "end " @@ -138,55 +140,69 @@ const char berry_prog[] = // Run rules, i.e. check each individual rule // Returns true if at least one rule matched, false if none "def exec_rules(ev_json) " - "import json " - "var ev = json.load(ev_json) " - "var ret = false " - "if ev == nil " - "print('BRY: ERROR, bad json: '+ev_json, 3) " - "else " - "for r: self._rules.keys() " - "ret = self.try_rule(ev,r,self._rules[r]) || ret " + "if self._rules " + "import json " + "var ev = json.load(ev_json) " + "var ret = false " + "if ev == nil " + "print('BRY: ERROR, bad json: '+ev_json, 3) " + "else " + "for r: self._rules.keys() " + "ret = self.try_rule(ev,r,self._rules[r]) || ret " + "end " "end " + "return ret " "end " - "return ret " + "return false " "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() " - "var i=0 " - "while i +#define BERRY_CONSOLE_CMD_DELIMITER "\x01" + const char kBrCommands[] PROGMEM = D_PRFX_BR "|" // prefix D_CMND_BR_RUN "|" D_CMND_BR_RESET ; @@ -32,14 +34,6 @@ void (* const BerryCommand[])(void) PROGMEM = { 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() // @@ -64,79 +58,13 @@ bool callBerryRule(void) { berry.rules_busy = true; char * json_event = TasmotaGlobal.mqtt_data; bool serviced = false; - - 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(); + serviced = callBerryEventDispatcher(PSTR("exec_rules"), nullptr, 0, TasmotaGlobal.mqtt_data); 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 } size_t callBerryGC(void) { - size_t ram_used = 0; - 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; + return callBerryEventDispatcher(PSTR("gc"), nullptr, 0, nullptr); } // void callBerryMqttData(void) { @@ -161,16 +89,79 @@ size_t callBerryGC(void) { // checkBeTop(); // } -// call a function (if exists) of type void -> void -void callBerryFunctionVoid(const char * fname) { - if (nullptr == berry.vm) { return; } - checkBeTop(); - be_getglobal(berry.vm, fname); +/* +// Call a method of a global object, with n args +// Before: stack must containt n args +// After: stack contains return value or nil if something wrong (args removes) +// 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)) { - 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(); + 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(); } +/*********************************************************************************************\ + * 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 = + "" + ; + +const char HTTP_BERRY_FORM_CMND[] PROGMEM = + "
" + "
" + "
Welcome to the Berry Scripting console. " + "Check the documentation." + "
" + "
" + // "" + // "

" + "
" + "" + // "
" + // "" + "" + "
"; + +const char HTTP_BTN_BERRY_CONSOLE[] PROGMEM = + "

"; + + +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), sizeof(LList_elt)+12); + // LList_elt * elt = (LList_elt*) ::operator new(sizeof(LList_elt) + 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 \*********************************************************************************************/ @@ -363,51 +661,63 @@ bool Xdrv52(uint8_t function) break; case FUNC_LOOP: if (!berry.autoexec_done) { - BrAutoexec(); + BrAutoexec(); // run autoexec.be at first tick, so we know all modules are initialized berry.autoexec_done = true; } 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: - callBerryFunctionVoid(PSTR("_run_deferred")); - break; - case FUNC_EVERY_100_MSECOND: - callBerryFunctionVoid(PSTR("every_100ms")); - break; - case FUNC_EVERY_SECOND: - callBerryFunctionVoid(PSTR("every_second")); + callBerryEventDispatcher(PSTR("every_50ms"), nullptr, 0, nullptr); break; case FUNC_COMMAND: result = DecodeCommand(kBrCommands, BerryCommand); if (!result) { - result = callBerryCommand(); + result = callBerryEventDispatcher(PSTR("cmd"), XdrvMailbox.topic, XdrvMailbox.index, XdrvMailbox.data); } 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: // break; - case FUNC_RULES_PROCESS: - result = callBerryRule(); - break; #ifdef USE_WEBSERVER case FUNC_WEB_ADD_BUTTON: + WSContentSend_P(HTTP_BTN_BERRY_CONSOLE); + callBerryEventDispatcher(PSTR("web_add_button"), nullptr, 0, nullptr); break; case FUNC_WEB_ADD_MAIN_BUTTON: - + callBerryEventDispatcher(PSTR("web_add_main_button"), nullptr, 0, nullptr); break; case FUNC_WEB_ADD_HANDLER: + callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr); + WebServer_on(PSTR("/bs"), HandleBerryConsole); break; #endif // USE_WEBSERVER case FUNC_SAVE_BEFORE_RESTART: - break; - case FUNC_MQTT_DATA: - // callBerryMqttData(); + callBerryEventDispatcher(PSTR("save_before_restart"), nullptr, 0, nullptr); break; case FUNC_WEB_SENSOR: + callBerryEventDispatcher(PSTR("web_sensor"), nullptr, 0, nullptr); break; case FUNC_JSON_APPEND: + callBerryEventDispatcher(PSTR("json_aooend"), nullptr, 0, nullptr); break; case FUNC_BUTTON_PRESSED: + callBerryEventDispatcher(PSTR("button_pressed"), nullptr, 0, nullptr); break;