2021-02-28 19:50:37 +00:00
/*
xdrv_52_9_berry . ino - Berry scripting language
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
# define XDRV_52 52
# include <berry.h>
2023-11-11 22:52:41 +00:00
extern " C " {
# include "be_bytecode.h"
# include "be_var.h"
}
2022-01-05 08:50:38 +00:00
# include "berry_tasmota.h"
2023-02-19 21:25:29 +00:00
# ifdef USE_MATTER_DEVICE
2023-02-03 23:00:21 +00:00
# include "berry_matter.h"
# endif
2023-12-25 10:01:19 +00:00
# ifdef USE_WS2812
# include "berry_animate.h"
# endif
2021-07-27 20:39:24 +01:00
# include "be_vm.h"
2021-11-10 18:31:22 +00:00
# include "ZipReadFS.h"
2022-02-21 21:22:30 +00:00
# include "ccronexpr.h"
2021-02-28 19:50:37 +00:00
2021-03-27 18:02:22 +00:00
extern " C " {
extern void be_load_custom_libs ( bvm * vm ) ;
2021-09-27 12:39:12 +01:00
extern void be_tracestack ( bvm * vm ) ;
2021-03-27 18:02:22 +00:00
}
2021-03-20 17:44:35 +00:00
2021-02-28 19:50:37 +00:00
const char kBrCommands [ ] PROGMEM = D_PRFX_BR " | " // prefix
2023-07-02 20:52:01 +01:00
D_CMND_BR_RUN " | " D_CMND_BR_RESTART
2021-02-28 19:50:37 +00:00
;
void ( * const BerryCommand [ ] ) ( void ) PROGMEM = {
2023-07-02 20:52:01 +01:00
CmndBrRun , CmndBrRestart
2021-02-28 19:50:37 +00:00
} ;
2021-07-29 18:58:23 +01:00
int32_t callBerryEventDispatcher ( const char * type , const char * cmd , int32_t idx , const char * payload , uint32_t data_len = 0 ) ;
2021-02-28 19:50:37 +00:00
//
// Sanity Check for be_top()
//
// Checks that the Berry stack is empty, if not print a Warning and empty it
//
void checkBeTop ( void ) {
int32_t top = be_top ( berry . vm ) ;
if ( top ! = 0 ) {
be_pop ( berry . vm , top ) ; // TODO should not be there
2022-01-10 17:37:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , D_LOG_BERRY " Error be_top is non zero=%d " , top ) ;
2021-02-28 19:50:37 +00:00
}
}
2021-03-21 16:12:10 +00:00
/*********************************************************************************************\
* Memory handler
* Use PSRAM if available
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
extern " C " {
2023-08-05 14:11:10 +01:00
void * berry_malloc ( size_t size ) ;
2021-03-21 16:12:10 +00:00
void * berry_realloc ( void * ptr , size_t size ) ;
# ifdef USE_BERRY_PSRAM
2023-08-05 14:11:10 +01:00
void * berry_malloc ( size_t size ) {
2021-03-21 16:12:10 +00:00
return special_malloc ( size ) ;
}
void * berry_realloc ( void * ptr , size_t size ) {
return special_realloc ( ptr , size ) ;
}
2021-05-09 17:15:15 +01:00
void * berry_calloc ( size_t num , size_t size ) {
return special_calloc ( num , size ) ;
}
2021-03-21 16:12:10 +00:00
# else
2023-08-05 14:11:10 +01:00
void * berry_malloc ( size_t size ) {
2021-03-21 16:12:10 +00:00
return malloc ( size ) ;
}
void * berry_realloc ( void * ptr , size_t size ) {
return realloc ( ptr , size ) ;
}
2021-05-09 17:15:15 +01:00
void * berry_calloc ( size_t num , size_t size ) {
return calloc ( num , size ) ;
}
2021-03-21 16:12:10 +00:00
# endif // USE_BERRY_PSRAM
2022-01-06 12:41:51 +00:00
2023-08-05 14:11:10 +01:00
void * berry_malloc32 ( size_t size ) {
2022-01-06 12:41:51 +00:00
# ifdef USE_BERRY_IRAM
return special_malloc32 ( size ) ;
# else
2022-02-15 21:51:11 +00:00
return NULL ; /* return NULL to indicate that IRAM is not enabled */
2022-01-06 12:41:51 +00:00
# endif
}
2021-05-09 17:15:15 +01:00
void berry_free ( void * ptr ) {
free ( ptr ) ;
}
2021-03-21 16:12:10 +00:00
}
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* Handlers for Berry calls and async
2021-04-04 11:04:36 +01:00
*
2021-02-28 19:50:37 +00:00
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// // call a function (if exists) of type void -> void
2021-08-19 11:37:19 +01:00
// If event == nullptr, then take XdrvMailbox.data
2021-10-19 21:38:54 +01:00
bool callBerryRule ( const char * event , bool teleperiod ) {
2022-04-30 11:48:06 +01:00
bool save_rules_busy = berry . rules_busy ;
bool exec_rule = ! save_rules_busy ; // true if the rule is executed, false if we only record the value
// if (berry.rules_busy) { return false; }
2021-02-28 19:50:37 +00:00
berry . rules_busy = true ;
bool serviced = false ;
2022-04-30 11:48:06 +01:00
serviced = callBerryEventDispatcher ( teleperiod ? " tele " : " rule " , nullptr , exec_rule , event ? event : XdrvMailbox . data ) ;
berry . rules_busy = save_rules_busy ;
2021-03-03 07:34:38 +00:00
return serviced ; // TODO event not handled
2021-02-28 19:50:37 +00:00
}
size_t callBerryGC ( void ) {
2021-03-20 17:44:35 +00:00
return callBerryEventDispatcher ( PSTR ( " gc " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
}
2021-03-20 17:44:35 +00:00
// call the event dispatcher from Tasmota object
2021-07-29 18:58:23 +01:00
// if data_len is non-zero, the event is also sent as raw `bytes()` object because the string may lose data
int32_t callBerryEventDispatcher ( const char * type , const char * cmd , int32_t idx , const char * payload , uint32_t data_len ) {
2021-03-20 17:44:35 +00:00
int32_t ret = 0 ;
2021-06-12 10:12:57 +01:00
bvm * vm = berry . vm ;
2021-03-20 17:44:35 +00:00
2021-06-12 10:12:57 +01:00
if ( nullptr = = vm ) { return ret ; }
2021-02-28 19:50:37 +00:00
checkBeTop ( ) ;
2021-06-12 10:12:57 +01:00
be_getglobal ( vm , PSTR ( " tasmota " ) ) ;
if ( ! be_isnil ( vm , - 1 ) ) {
be_getmethod ( vm , - 1 , PSTR ( " event " ) ) ;
if ( ! be_isnil ( vm , - 1 ) ) {
be_pushvalue ( vm , - 2 ) ; // add instance as first arg
be_pushstring ( vm , type ! = nullptr ? type : " " ) ;
be_pushstring ( vm , cmd ! = nullptr ? cmd : " " ) ;
be_pushint ( vm , idx ) ;
2021-12-01 12:52:48 +00:00
be_pushstring ( vm , payload ! = nullptr ? payload : " " ) ; // empty json
2021-10-19 19:14:31 +01:00
BrTimeoutStart ( ) ;
2021-07-29 18:58:23 +01:00
if ( data_len > 0 ) {
be_pushbytes ( vm , payload , data_len ) ; // if data_len is set, we also push raw bytes
ret = be_pcall ( vm , 6 ) ; // 6 arguments
be_pop ( vm , 1 ) ;
} else {
ret = be_pcall ( vm , 5 ) ; // 5 arguments
}
2021-10-19 19:14:31 +01:00
BrTimeoutReset ( ) ;
2021-06-12 10:12:57 +01:00
if ( ret ! = 0 ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-06-12 10:12:57 +01:00
return ret ;
}
be_pop ( vm , 5 ) ;
if ( be_isint ( vm , - 1 ) | | be_isbool ( vm , - 1 ) ) {
if ( be_isint ( vm , - 1 ) ) { ret = be_toint ( vm , - 1 ) ; }
if ( be_isbool ( vm , - 1 ) ) { ret = be_tobool ( vm , - 1 ) ; }
2021-03-20 17:44:35 +00:00
}
}
2021-06-12 10:12:57 +01:00
be_pop ( vm , 1 ) ; // remove method
2021-02-28 19:50:37 +00:00
}
2021-06-12 10:12:57 +01:00
be_pop ( vm , 1 ) ; // remove instance object
2021-02-28 19:50:37 +00:00
checkBeTop ( ) ;
2021-03-20 17:44:35 +00:00
return ret ;
2021-02-28 19:50:37 +00:00
}
2022-01-09 18:22:23 +00:00
// Simplified version of event loop. Just call `tasmota.fast_loop()`
2023-09-02 10:10:50 +01:00
// `every_5ms` is a flag to wait at least 5ms between calss to `tasmota.fast_loop()`
void callBerryFastLoop ( bool every_5ms ) {
static uint32_t fast_loop_last_call = 0 ;
2022-01-09 18:22:23 +00:00
bvm * vm = berry . vm ;
if ( nullptr = = vm ) { return ; }
2023-09-02 10:10:50 +01:00
uint32_t now = millis ( ) ;
if ( every_5ms ) {
if ( ! TimeReached ( fast_loop_last_call + USE_BERRY_FAST_LOOP_SLEEP_MS /* 5ms */ ) ) { return ; }
}
fast_loop_last_call = now ;
// TODO - can we make this dereferencing once for all?
2022-01-09 18:22:23 +00:00
if ( be_getglobal ( vm , " tasmota " ) ) {
if ( be_getmethod ( vm , - 1 , " fast_loop " ) ) {
be_pushvalue ( vm , - 2 ) ; // add instance as first arg
BrTimeoutStart ( ) ;
int32_t ret = be_pcall ( vm , 1 ) ;
if ( ret ! = 0 ) {
be_error_pop_all ( berry . vm ) ; // clear Berry stack
}
BrTimeoutReset ( ) ;
be_pop ( vm , 1 ) ;
}
be_pop ( vm , 1 ) ; // remove method
}
be_pop ( vm , 1 ) ; // remove instance object
be_pop ( vm , be_top ( vm ) ) ; // clean
}
2021-03-03 07:34:38 +00:00
/*********************************************************************************************\
* VM Observability
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-04-19 18:37:59 +01:00
void BerryObservability ( bvm * vm , int event . . . ) ;
void BerryObservability ( bvm * vm , int event . . . ) {
2021-03-03 07:34:38 +00:00
va_list param ;
va_start ( param , event ) ;
static int32_t vm_usage = 0 ;
static uint32_t gc_time = 0 ;
switch ( event ) {
2021-12-12 17:56:11 +00:00
case BE_OBS_PCALL_ERROR : // error after be_pcall
{
int32_t top = be_top ( vm ) ;
// check if we have two strings for an Exception
if ( top > = 2 & & be_isstring ( vm , - 1 ) & & be_isstring ( vm , - 2 ) ) {
berry_log_C ( PSTR ( D_LOG_BERRY " Exception> '%s' - %s " ) , be_tostring ( berry . vm , - 2 ) , be_tostring ( berry . vm , - 1 ) ) ;
be_tracestack ( vm ) ;
} else {
be_dumpstack ( vm ) ;
}
}
2021-03-03 07:34:38 +00:00
case BE_OBS_GC_START :
{
gc_time = millis ( ) ;
vm_usage = va_arg ( param , int32_t ) ;
}
break ;
case BE_OBS_GC_END :
{
int32_t vm_usage2 = va_arg ( param , int32_t ) ;
uint32_t gc_elapsed = millis ( ) - gc_time ;
2021-11-20 08:49:23 +00:00
uint32_t vm_scanned = va_arg ( param , uint32_t ) ;
uint32_t vm_freed = va_arg ( param , uint32_t ) ;
2022-02-15 21:51:11 +00:00
size_t slots_used_before_gc = va_arg ( param , size_t ) ;
size_t slots_allocated_before_gc = va_arg ( param , size_t ) ;
size_t slots_used_after_gc = va_arg ( param , size_t ) ;
size_t slots_allocated_after_gc = va_arg ( param , size_t ) ;
AddLog ( LOG_LEVEL_DEBUG_MORE , D_LOG_BERRY " GC from %i to %i bytes, objects freed %i/%i (in %d ms) - slots from %i/%i to %i/%i " ,
vm_usage , vm_usage2 , vm_freed , vm_scanned , gc_elapsed ,
slots_used_before_gc , slots_allocated_before_gc ,
slots_used_after_gc , slots_allocated_after_gc ) ;
2022-08-26 18:23:21 +01:00
2022-11-03 21:01:24 +00:00
# ifdef UBE_BERRY_DEBUG_GC
2022-08-26 18:23:21 +01:00
// Add more in-deptch metrics
AddLog ( LOG_LEVEL_DEBUG_MORE , D_LOG_BERRY " GC timing (us) 1:%i 2:%i 3:%i 4:%i 5:%i total:%i " ,
vm - > micros_gc1 - vm - > micros_gc0 ,
vm - > micros_gc2 - vm - > micros_gc1 ,
vm - > micros_gc3 - vm - > micros_gc2 ,
vm - > micros_gc4 - vm - > micros_gc3 ,
vm - > micros_gc5 - vm - > micros_gc4 ,
vm - > micros_gc5 - vm - > micros_gc0
) ;
AddLog ( LOG_LEVEL_DEBUG_MORE , D_LOG_BERRY " GC by type "
" string:%i class:%i proto:%i instance:%i map:%i "
" list:%i closure:%i ntvclos:%i module:%i comobj:%i " ,
vm - > gc_mark_string ,
vm - > gc_mark_class ,
vm - > gc_mark_proto ,
vm - > gc_mark_instance ,
vm - > gc_mark_map ,
vm - > gc_mark_list ,
vm - > gc_mark_closure ,
vm - > gc_mark_ntvclos ,
vm - > gc_mark_module ,
vm - > gc_mark_comobj
) ;
2022-11-03 21:01:24 +00:00
# endif
2021-10-07 18:03:34 +01:00
// make new threshold tighter when we reach high memory usage
if ( ! UsePSRAM ( ) & & vm - > gc . threshold > 20 * 1024 ) {
vm - > gc . threshold = vm - > gc . usage + 10 * 1024 ; // increase by only 10 KB
}
2021-03-03 07:34:38 +00:00
}
break ;
2021-10-26 22:13:16 +01:00
case BE_OBS_STACK_RESIZE_START :
{
int32_t stack_before = va_arg ( param , int32_t ) ;
int32_t stack_after = va_arg ( param , int32_t ) ;
2022-01-10 09:43:13 +00:00
AddLog ( LOG_LEVEL_DEBUG_MORE , PSTR ( D_LOG_BERRY " Stack resized from %i to %i bytes " ) , stack_before , stack_after ) ;
2021-10-26 22:13:16 +01:00
}
break ;
2021-10-19 19:14:31 +01:00
case BE_OBS_VM_HEARTBEAT :
{
// AddLog(LOG_LEVEL_INFO, ">>>: Heartbeat now=%i timeout=%i", millis(), berry.timeout);
if ( berry . timeout ) {
if ( TimeReached ( berry . timeout ) ) {
be_raise ( vm , " timeout_error " , " Berry code running for too long " ) ;
}
}
}
break ;
2024-02-25 20:17:08 +00:00
case BE_OBS_MALLOC_FAIL :
{
int32_t vm_usage2 = va_arg ( param , int32_t ) ;
AddLog ( LOG_LEVEL_ERROR , D_LOG_BERRY " *** MEMORY ALLOCATION FAILED *** usage %i bytes " , vm_usage2 ) ;
}
break ;
2021-10-19 19:14:31 +01:00
default :
break ;
2021-03-03 07:34:38 +00:00
}
va_end ( param ) ;
}
2021-11-22 18:29:53 +00:00
/*********************************************************************************************\
* Adde Berry metrics to teleperiod
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void BrShowState ( void ) ;
void BrShowState ( void ) {
2022-02-14 13:53:42 +00:00
if ( berry . vm ) {
// trigger a gc first
be_gc_collect ( berry . vm ) ;
ResponseAppend_P ( PSTR ( " , \" Berry \" :{ \" HeapUsed \" :%u, \" Objects \" :%u} " ) ,
berry . vm - > gc . usage / 1024 , berry . vm - > counter_gc_kept ) ;
}
2021-11-22 18:29:53 +00:00
}
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* VM Init
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2023-07-06 20:37:46 +01:00
extern " C " void be_webserver_cb_deinit ( bvm * vm ) ;
2021-05-25 17:53:10 +01:00
void BerryInit ( void ) {
2021-02-28 19:50:37 +00:00
// clean previous VM if any
if ( berry . vm ! = nullptr ) {
2023-07-02 20:52:01 +01:00
be_cb_deinit ( berry . vm ) ; // deregister any C callback for this VM
2023-07-06 20:37:46 +01:00
# ifdef USE_WEBSERVER
be_webserver_cb_deinit ( berry . vm ) ; // deregister C callbacks managed by webserver
# endif // USE_WEBSERVER
2021-02-28 19:50:37 +00:00
be_vm_delete ( berry . vm ) ;
berry . vm = nullptr ;
2023-07-06 20:37:46 +01:00
berry . web_add_handler_done = false ;
2023-07-02 20:52:01 +01:00
berry . autoexec_done = false ;
berry . repl_active = false ;
berry . rules_busy = false ;
berry . timeout = 0 ;
2021-02-28 19:50:37 +00:00
}
int32_t ret_code1 , ret_code2 ;
bool berry_init_ok = false ;
2021-04-04 11:04:36 +01:00
do {
2021-02-28 19:50:37 +00:00
berry . vm = be_vm_new ( ) ; /* create a virtual machine instance */
2021-11-04 18:34:22 +00:00
be_set_obs_hook ( berry . vm , & BerryObservability ) ; /* attach observability hook */
2022-08-26 18:23:21 +01:00
be_set_obs_micros ( berry . vm , ( bmicrosfnct ) & micros ) ;
2021-08-16 19:46:09 +01:00
comp_set_named_gbl ( berry . vm ) ; /* Enable named globals in Berry compiler */
2021-11-04 18:34:22 +00:00
comp_set_strict ( berry . vm ) ; /* Enable strict mode in Berry compiler, equivalent of `import strict` */
2022-01-19 21:56:11 +00:00
be_set_ctype_func_hanlder ( berry . vm , be_call_ctype_func ) ;
2021-02-28 19:50:37 +00:00
2022-08-30 17:11:04 +01:00
if ( UsePSRAM ( ) ) { // if PSRAM is available, raise the max size to 512kb
berry . vm - > bytesmaxsize = 512 * 1024 ;
}
2021-11-04 18:34:22 +00:00
be_load_custom_libs ( berry . vm ) ; // load classes and modules
2021-02-28 19:50:37 +00:00
2021-11-04 18:34:22 +00:00
// Set the GC threshold to 3584 bytes to avoid the first useless GC
berry . vm - > gc . threshold = 3584 ;
2021-02-28 19:50:37 +00:00
ret_code1 = be_loadstring ( berry . vm , berry_prog ) ;
if ( ret_code1 ! = 0 ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-02-28 19:50:37 +00:00
break ;
}
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Berry code loaded, RAM used=%u"), be_gc_memcount(berry.vm));
ret_code2 = be_pcall ( berry . vm , 0 ) ;
if ( ret_code1 ! = 0 ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-02-28 19:50:37 +00:00
break ;
}
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Berry code ran, RAM used=%u"), be_gc_memcount(berry.vm));
2021-06-12 10:12:57 +01:00
if ( be_top ( berry . vm ) > 1 ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-06-12 10:12:57 +01:00
} else {
be_pop ( berry . vm , 1 ) ;
2021-04-03 18:53:52 +01:00
}
2021-02-28 19:50:37 +00:00
2023-11-04 10:46:25 +00:00
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_BERRY " Berry initialized, RAM used %u bytes " ) , callBerryGC ( ) ) ;
2021-02-28 19:50:37 +00:00
berry_init_ok = true ;
2021-05-25 17:53:10 +01:00
2022-01-10 09:28:55 +00:00
// we generate a synthetic event `autoexec`
2021-11-16 20:46:42 +00:00
callBerryEventDispatcher ( PSTR ( " preinit " ) , nullptr , 0 , nullptr ) ;
2022-01-10 09:28:55 +00:00
2021-05-25 17:53:10 +01:00
// Run pre-init
2021-06-12 10:12:57 +01:00
BrLoad ( " preinit.be " ) ; // run 'preinit.be' if present
2021-02-28 19:50:37 +00:00
} while ( 0 ) ;
if ( ! berry_init_ok ) {
// free resources
if ( berry . vm ! = nullptr ) {
be_vm_delete ( berry . vm ) ;
berry . vm = nullptr ;
}
}
}
2023-07-02 20:52:01 +01:00
/*********************************************************************************************\
* BrRestart - restart a fresh new Berry vm , unloading everything from previous VM
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void CmndBrRestart ( void ) {
if ( berry . vm = = nullptr ) {
ResponseCmndChar_P ( " Berry VM not started " ) ;
}
BerryInit ( ) ;
ResponseCmndChar_P ( " Berry VM restarted " ) ;
}
2021-05-25 17:53:10 +01:00
/*********************************************************************************************\
* Execute a script in Flash file - system
2021-05-30 21:32:37 +01:00
*
2021-05-25 17:53:10 +01:00
* Two options supported :
* berry_preinit : load " preinit.be " to configure the device before driver pre - init and init
* ( typically I2C drivers , and AXP192 / AXP202 configuration )
* berry_autoexec : load " autoexec.be " once all drivers are initialized
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2021-06-12 10:12:57 +01:00
void BrLoad ( const char * script_name ) {
2021-05-25 17:53:10 +01:00
if ( berry . vm = = nullptr | | TasmotaGlobal . no_autoexec ) { return ; } // abort is berry is not running, or bootloop prevention kicked in
2021-03-13 21:42:24 +00:00
2023-05-25 16:13:31 +01:00
if ( ! strcmp_P ( script_name , " autoexec.be " ) ) {
2023-05-26 16:47:57 +01:00
if ( Settings - > flag6 . berry_no_autoexec ) { // SetOption153 - (Berry) Disable autoexec.be on restart (1)
2023-05-25 16:13:31 +01:00
return ;
}
}
2021-06-12 10:12:57 +01:00
be_getglobal ( berry . vm , PSTR ( " load " ) ) ;
if ( ! be_isnil ( berry . vm , - 1 ) ) {
be_pushstring ( berry . vm , script_name ) ;
2021-10-19 19:14:31 +01:00
BrTimeoutStart ( ) ;
2021-06-12 10:12:57 +01:00
if ( be_pcall ( berry . vm , 1 ) ! = 0 ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-06-12 10:12:57 +01:00
return ;
}
2021-10-19 19:14:31 +01:00
BrTimeoutReset ( ) ;
2021-06-12 10:12:57 +01:00
bool loaded = be_tobool ( berry . vm , - 2 ) ; // did it succeed?
2021-03-13 21:42:24 +00:00
be_pop ( berry . vm , 2 ) ;
2021-06-12 10:12:57 +01:00
if ( loaded ) {
2022-01-10 17:37:28 +00:00
AddLog ( LOG_LEVEL_INFO , D_LOG_BERRY " Successfully loaded '%s' " , script_name ) ;
2021-06-12 10:12:57 +01:00
} else {
2022-01-10 17:37:28 +00:00
AddLog ( LOG_LEVEL_DEBUG , D_LOG_BERRY " No '%s' " , script_name ) ;
2021-06-12 10:12:57 +01:00
}
2021-03-13 21:42:24 +00:00
}
}
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* Tasmota Commands
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
//
// Command `BrRun`
//
void CmndBrRun ( void ) {
int32_t ret_code ;
2022-06-03 21:57:05 +01:00
const char * ret_val ;
2021-02-28 19:50:37 +00:00
if ( berry . vm = = nullptr ) { ResponseCmndChar_P ( PSTR ( D_BR_NOT_STARTED ) ) ; return ; }
char br_cmd [ XdrvMailbox . data_len + 12 ] ;
// encapsulate into a function, copied from `be_repl.c` / `try_return()`
snprintf_P ( br_cmd , sizeof ( br_cmd ) , PSTR ( " return (%s) " ) , XdrvMailbox . data ) ;
checkBeTop ( ) ;
do {
// First try with the `return ()` wrapper
ret_code = be_loadbuffer ( berry . vm , PSTR ( " input " ) , br_cmd , strlen ( br_cmd ) ) ;
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 " ) , XdrvMailbox . data , strlen ( XdrvMailbox . data ) ) ;
}
if ( 0 ! = ret_code ) break ;
2021-10-19 19:14:31 +01:00
BrTimeoutStart ( ) ;
2021-02-28 19:50:37 +00:00
ret_code = be_pcall ( berry . vm , 0 ) ; // execute code
2021-10-19 19:14:31 +01:00
BrTimeoutReset ( ) ;
2021-02-28 19:50:37 +00:00
} while ( 0 ) ;
if ( 0 = = ret_code ) {
// code taken from REPL, look first at top, and if nil, look at return value
2021-03-13 21:42:24 +00:00
// if (!be_isnil(berry.vm, 1)) {
2021-03-07 18:37:18 +00:00
ret_val = be_tostring ( berry . vm , 1 ) ;
2021-03-13 21:42:24 +00:00
// } else {
// ret_val = be_tostring(berry.vm, 2);
// }
2021-03-07 18:37:18 +00:00
Response_P ( " { \" " D_PRFX_BR " \" : \" %s \" } " , EscapeJSONString ( ret_val ) . c_str ( ) ) ; // can't use XdrvMailbox.command as it may have been overwritten by subcommand
2021-02-28 19:50:37 +00:00
be_pop ( berry . vm , 1 ) ;
} else {
2021-03-07 18:37:18 +00:00
Response_P ( PSTR ( " { \" " D_PRFX_BR " \" : \" [%s] %s \" } " ) , EscapeJSONString ( be_tostring ( berry . vm , - 2 ) ) . c_str ( ) , EscapeJSONString ( be_tostring ( berry . vm , - 1 ) ) . c_str ( ) ) ;
2021-02-28 19:50:37 +00:00
be_pop ( berry . vm , 2 ) ;
}
checkBeTop ( ) ;
}
2021-03-20 17:44:35 +00:00
/*********************************************************************************************\
* 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 ;
2021-04-04 11:04:36 +01:00
2021-03-20 17:44:35 +00:00
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
2021-10-19 19:14:31 +01:00
BrTimeoutStart ( ) ;
2021-03-20 17:44:35 +00:00
ret_code = be_pcall ( berry . vm , 0 ) ; // execute code
2021-10-19 19:14:31 +01:00
BrTimeoutReset ( ) ;
2021-03-20 17:44:35 +00:00
// 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 " ) ;
2021-06-05 10:47:09 +01:00
// AddLog(LOG_LEVEL_INFO, PSTR(">>> %s"), ret_val);
2021-03-20 17:44:35 +00:00
}
be_pop ( berry . vm , 1 ) ;
}
}
if ( BE_EXCEPTION = = ret_code ) {
2021-12-12 17:56:11 +00:00
be_error_pop_all ( berry . vm ) ; // clear Berry stack
2021-03-20 17:44:35 +00:00
}
} 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
2021-04-04 11:04:36 +01:00
2021-03-20 17:44:35 +00:00
" 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=''; "
2022-04-03 14:25:07 +01:00
" t.scrollTop=1e8; "
2021-03-20 17:44:35 +00:00
" 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(); "
2022-04-03 14:25:07 +01:00
" x.onreadystatechange=()=>{ "
2021-03-20 17:44:35 +00:00
" if(x.readyState==4&&x.status==200){ "
" var d,t1; "
2022-02-16 21:03:58 +00:00
" d=x.responseText.split(/ " BERRY_CONSOLE_CMD_DELIMITER " /,2); " // Field separator
" var d1=d.length>1?d[0]:null; "
2021-03-20 17:44:35 +00:00
" if(d1){ "
" t1=document.createElement('div'); "
" t1.classList.add('br1'); "
" t1.innerText=d1; "
" t.appendChild(t1); "
" } "
2022-02-16 21:03:58 +00:00
" d1=d.length>1?d[1]:d[0]; "
2021-03-20 17:44:35 +00:00
" if(d1){ "
" t1=document.createElement('div'); "
" t1.classList.add('br2'); "
" t1.innerText=d1; "
" t.appendChild(t1); "
" } "
2022-04-03 14:25:07 +01:00
" t.scrollTop=1e8; "
2021-03-20 17:44:35 +00:00
" sn=t.scrollTop; "
" clearTimeout(ft); "
" lt=setTimeout(l,ltm); " // webrefresh timer....
" } "
" }; "
2021-06-11 16:09:03 +01:00
" x.open('GET','bc?c2='+id+o,true); " // Related to Webserver->hasArg("c2") and WebGetArg("c2", stmp, sizeof(stmp))
2021-03-20 17:44:35 +00:00
" x.send(); "
2022-04-03 14:25:07 +01:00
" ft=setTimeout(l,2e4); " // fail timeout, triggered 20s after asking for XHR
2021-03-20 17:44:35 +00:00
" }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
" }); "
" } "
2021-04-04 11:04:36 +01:00
" wl(h); " ; // Add console command key eventlistener after name has been synced with id (= wl(jd))
2021-03-20 17:44:35 +00:00
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. "
2021-06-16 07:37:28 +01:00
" Check the <a href='https://tasmota.github.io/docs/Berry/' target='_blank'>documentation</a>. "
2021-03-20 17:44:35 +00:00
" </div> "
" </div> "
" <form method='get' id='fo' onsubmit='return l(1);'> "
" <textarea id='c1' class='br0 bri' rows='4' cols='340' wrap='soft' autofocus required></textarea> "
" <button type='submit'>Run code (or press 'Enter' twice)</button> "
2023-07-02 20:52:01 +01:00
" </form> "
# ifdef USE_BERRY_DEBUG
" <p><form method='post' > "
" <button type='submit' name='rst' class='bred' onclick= \" if(confirm('Confirm removing endpoint')){clearTimeout(lt);return true;}else{return false;} \" >Restart Berry VM (for devs only)</button> "
" </form></p> "
# endif // USE_BERRY_DEBUG
;
2021-03-20 17:44:35 +00:00
const char HTTP_BTN_BERRY_CONSOLE [ ] PROGMEM =
2021-06-11 16:09:03 +01:00
" <p><form action='bc' method='get'><button>Berry Scripting console</button></form></p> " ;
2021-03-20 17:44:35 +00:00
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
2021-06-05 10:47:09 +01:00
// AddLog(LOG_LEVEL_INFO, PSTR("BRY: received command %s"), svalue.c_str());
2021-03-20 17:44:35 +00:00
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 ) {
2021-11-29 20:51:46 +00:00
_WSContentSend ( l . getBuffer ( ) ) ;
2021-03-20 17:44:35 +00:00
}
berry . log . reset ( ) ;
}
WSContentEnd ( ) ;
}
void HandleBerryConsole ( void )
{
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
if ( Webserver - > hasArg ( F ( " c2 " ) ) ) { // Console refresh requested
HandleBerryConsoleRefresh ( ) ;
return ;
}
2023-07-02 20:52:01 +01:00
if ( Webserver - > hasArg ( F ( " rst " ) ) ) { // restart VM
BerryInit ( ) ;
Webserver - > sendHeader ( " Location " , " /bc " , true ) ;
Webserver - > send ( 302 , " text/plain " , " " ) ;
}
2021-03-20 17:44:35 +00:00
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_HTTP " Berry " D_CONSOLE ) ) ;
WSContentStart_P ( PSTR ( " Berry " D_CONSOLE ) ) ;
2021-06-11 17:14:12 +01:00
WSContentSend_P ( HTTP_SCRIPT_BERRY_CONSOLE , Settings - > web_refresh ) ;
2021-03-20 17:44:35 +00:00
WSContentSend_P ( HTTP_SCRIPT_BERRY_CONSOLE2 ) ;
WSContentSendStyle ( ) ;
WSContentFlush ( ) ;
_WSContentSend ( HTTP_BERRY_STYLE_CMND ) ;
_WSContentSend ( HTTP_BERRY_FORM_CMND ) ;
2021-04-16 10:40:38 +01:00
WSContentSpaceButton ( BUTTON_MANAGEMENT ) ;
2021-03-20 17:44:35 +00:00
WSContentStop ( ) ;
}
2024-01-08 08:30:18 +00:00
// const BeBECCode_t BECCode[] = {
// struct BeBECCode_t {
// const char * display_name; // display name in Web UI (must be URL encoded)
// const char * id; // id in requested URL
// const char * url; // absolute URL to download the bec file
// const char * redirect; // relative URI to redirect after loading
// };
// Display Buttons to dynamically load bec files
void HandleBerryBECLoaderButton ( void ) {
2023-11-11 22:52:41 +00:00
bvm * vm = berry . vm ;
2024-01-08 08:30:18 +00:00
for ( int32_t i = 0 ; i < ARRAY_SIZE ( BECCode ) ; i + + ) {
const BeBECCode_t & bec = BECCode [ i ] ;
if ( ! ( * bec . loaded ) ) {
if ( be_global_find ( vm , be_newstr ( vm , bec . id ) ) < 0 ) { // the global name doesn't exist
WSContentSend_P ( " <form id=but_part_mgr style='display: block;' action='tapp' method='get'><input type='hidden' name='n' value='%s'/><button>[Load %s]</button></form><p></p> " , bec . id , bec . display_name ) ;
} else {
* bec . loaded = true ;
}
2023-11-11 22:52:41 +00:00
}
}
}
2024-01-06 16:32:50 +00:00
extern " C " bbool BerryBECLoader ( const char * url ) ;
2024-01-08 08:30:18 +00:00
void HandleBerryBECLoader ( void ) {
String n = Webserver - > arg ( " n " ) ;
for ( int32_t i = 0 ; i < ARRAY_SIZE ( BECCode ) ; i + + ) {
const BeBECCode_t & bec = BECCode [ i ] ;
if ( n . equals ( bec . id ) ) {
if ( BerryBECLoader ( bec . url ) ) {
// All good, redirect
Webserver - > sendHeader ( " Location " , bec . redirect , true ) ;
Webserver - > send ( 302 , " text/plain " , " " ) ;
* bec . loaded = true ;
} else {
Webserver - > sendHeader ( " Location " , " /mn? " , true ) ;
Webserver - > send ( 302 , " text/plain " , " " ) ;
}
}
2023-11-11 22:52:41 +00:00
}
}
// return true if successful
2024-01-06 16:32:50 +00:00
extern " C " bbool BerryBECLoader ( const char * url ) {
2023-11-11 22:52:41 +00:00
bvm * vm = berry . vm ;
HTTPClientLight cl ;
cl . setUserAgent ( USE_BERRY_WEBCLIENT_USERAGENT ) ;
cl . setConnectTimeout ( USE_BERRY_WEBCLIENT_TIMEOUT ) ; // set default timeout
cl . setFollowRedirects ( HTTPC_STRICT_FOLLOW_REDIRECTS ) ;
if ( ! cl . begin ( url ) ) {
AddLog ( LOG_LEVEL_INFO , " BRY: unable to load URL '%s' " , url ) ;
// cl.end();
return false ;
}
uint32_t http_connect_time = millis ( ) ;
int32_t httpCode = cl . GET ( ) ;
if ( httpCode ! = 200 ) {
AddLog ( LOG_LEVEL_INFO , " BRY: unable to load URL '%s' code %i " , url , httpCode ) ;
// cl.end();
return false ;
}
int32_t sz = cl . getSize ( ) ;
AddLog ( LOG_LEVEL_DEBUG , " BRY: Response http_code %i size %i bytes in %i ms " , httpCode , sz , millis ( ) - http_connect_time ) ;
// abort if we exceed 32KB size, things will not go well otherwise
if ( sz > = 32767 | | sz < = 0 ) {
AddLog ( LOG_LEVEL_DEBUG , " BRY: Response size too big %i bytes " , sz ) ;
return false ;
}
// create a bytes object at top of stack.
// the streamwriter knows how to get it.
uint8_t * buf = ( uint8_t * ) be_pushbytes ( vm , nullptr , sz ) ;
StreamBeBytesWriter memory_writer ( vm ) ;
int32_t written = cl . writeToStream ( & memory_writer ) ;
cl . end ( ) ; // free allocated memory ~16KB
size_t loaded_sz = 0 ;
const void * loaded_buf = be_tobytes ( vm , - 1 , & loaded_sz ) ;
FlashFileImplPtr fp = FlashFileImplPtr ( new FlashFileImpl ( loaded_buf , loaded_sz ) ) ;
File * f_ptr = new File ( fp ) ; // we need to allocate dynamically because be_close calls `delete` on it
bclosure * loaded_bec = be_bytecode_load_from_fs ( vm , f_ptr ) ;
be_pop ( vm , 1 ) ;
if ( loaded_bec ! = NULL ) {
be_pushclosure ( vm , loaded_bec ) ;
be_call ( vm , 0 ) ;
be_pop ( vm , 1 ) ;
}
be_gc_collect ( vm ) ; // force a GC to free the buffer now
return true ;
}
2021-03-20 17:44:35 +00:00
# endif // USE_WEBSERVER
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2022-11-11 09:44:56 +00:00
bool Xdrv52 ( uint32_t function )
2021-02-28 19:50:37 +00:00
{
bool result = false ;
switch ( function ) {
2023-09-02 10:10:50 +01:00
case FUNC_SLEEP_LOOP :
if ( TasmotaGlobal . berry_fast_loop_enabled ) { // call only if enabled at global level
callBerryFastLoop ( true ) ; // call `tasmota.fast_loop()` optimized for minimal performance impact
}
break ;
2021-03-13 21:42:24 +00:00
case FUNC_LOOP :
if ( ! berry . autoexec_done ) {
2022-01-10 09:28:55 +00:00
// we generate a synthetic event `autoexec`
2021-11-15 22:06:04 +00:00
callBerryEventDispatcher ( PSTR ( " autoexec " ) , nullptr , 0 , nullptr ) ;
2021-06-12 10:12:57 +01:00
BrLoad ( " autoexec.be " ) ; // run autoexec.be at first tick, so we know all modules are initialized
2021-03-13 21:42:24 +00:00
berry . autoexec_done = true ;
2023-07-06 20:37:46 +01:00
// check if `web_add_handler` was missed, for example because of Berry VM restart
if ( ! berry . web_add_handler_done ) {
bool network_up = WifiHasIP ( ) ;
# ifdef USE_ETHERNET
network_up = network_up | | EthernetHasIP ( ) ;
# endif
if ( network_up ) { // if network is already up, send a synthetic event to trigger web handlers
callBerryEventDispatcher ( PSTR ( " web_add_handler " ) , nullptr , 0 , nullptr ) ;
berry . web_add_handler_done = true ;
}
}
2021-03-13 21:42:24 +00:00
}
2022-01-09 18:22:23 +00:00
if ( TasmotaGlobal . berry_fast_loop_enabled ) { // call only if enabled at global level
2023-09-02 10:10:50 +01:00
callBerryFastLoop ( false ) ; // call `tasmota.fast_loop()` optimized for minimal performance impact
2022-01-09 18:22:23 +00:00
}
2021-03-13 21:42:24 +00:00
break ;
2021-03-20 17:44:35 +00:00
// Berry wide commands and events
case FUNC_RULES_PROCESS :
2021-10-19 21:38:54 +01:00
result = callBerryRule ( nullptr , false ) ;
break ;
case FUNC_TELEPERIOD_RULES_PROCESS :
result = callBerryRule ( nullptr , true ) ;
2021-02-28 19:50:37 +00:00
break ;
2021-03-20 17:44:35 +00:00
case FUNC_MQTT_DATA :
2021-07-29 18:58:23 +01:00
result = callBerryEventDispatcher ( PSTR ( " mqtt_data " ) , XdrvMailbox . topic , 0 , XdrvMailbox . data , XdrvMailbox . data_len ) ;
2022-01-05 11:28:07 +00:00
break ;
2021-02-28 19:50:37 +00:00
case FUNC_COMMAND :
result = DecodeCommand ( kBrCommands , BerryCommand ) ;
if ( ! result ) {
2021-03-20 17:44:35 +00:00
result = callBerryEventDispatcher ( PSTR ( " cmd " ) , XdrvMailbox . topic , XdrvMailbox . index , XdrvMailbox . data ) ;
2021-02-28 19:50:37 +00:00
}
break ;
2021-04-04 11:04:36 +01:00
2021-03-20 17:44:35 +00:00
// Module specific events
2021-11-18 19:30:16 +00:00
case FUNC_EVERY_50_MSECOND :
callBerryEventDispatcher ( PSTR ( " every_50ms " ) , nullptr , 0 , nullptr ) ;
break ;
2021-03-20 17:44:35 +00:00
case FUNC_EVERY_100_MSECOND :
callBerryEventDispatcher ( PSTR ( " every_100ms " ) , nullptr , 0 , nullptr ) ;
break ;
2022-01-08 17:13:50 +00:00
case FUNC_EVERY_250_MSECOND :
callBerryEventDispatcher ( PSTR ( " every_250ms " ) , nullptr , 0 , nullptr ) ;
break ;
2021-03-20 17:44:35 +00:00
case FUNC_EVERY_SECOND :
callBerryEventDispatcher ( PSTR ( " every_second " ) , nullptr , 0 , nullptr ) ;
break ;
2021-12-01 12:52:48 +00:00
case FUNC_SET_DEVICE_POWER :
result = callBerryEventDispatcher ( PSTR ( " set_power_handler " ) , nullptr , XdrvMailbox . index , nullptr ) ;
2021-11-29 22:21:21 +00:00
break ;
2021-02-28 19:50:37 +00:00
# ifdef USE_WEBSERVER
2021-04-16 10:40:38 +01:00
case FUNC_WEB_ADD_CONSOLE_BUTTON :
if ( XdrvMailbox . index ) {
XdrvMailbox . index + + ;
} else {
WSContentSend_P ( HTTP_BTN_BERRY_CONSOLE ) ;
2024-01-08 08:30:18 +00:00
HandleBerryBECLoaderButton ( ) ; // display buttons to load BEC files
2021-04-16 10:40:38 +01:00
callBerryEventDispatcher ( PSTR ( " web_add_button " ) , nullptr , 0 , nullptr ) ;
2021-06-30 21:36:51 +01:00
callBerryEventDispatcher ( PSTR ( " web_add_console_button " ) , nullptr , 0 , nullptr ) ;
2021-04-16 10:40:38 +01:00
}
2021-02-28 19:50:37 +00:00
break ;
case FUNC_WEB_ADD_MAIN_BUTTON :
2021-03-20 17:44:35 +00:00
callBerryEventDispatcher ( PSTR ( " web_add_main_button " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
2021-06-30 21:36:51 +01:00
case FUNC_WEB_ADD_MANAGEMENT_BUTTON :
callBerryEventDispatcher ( PSTR ( " web_add_management_button " ) , nullptr , 0 , nullptr ) ;
break ;
case FUNC_WEB_ADD_BUTTON :
callBerryEventDispatcher ( PSTR ( " web_add_config_button " ) , nullptr , 0 , nullptr ) ;
break ;
2021-02-28 19:50:37 +00:00
case FUNC_WEB_ADD_HANDLER :
2023-07-06 20:37:46 +01:00
if ( ! berry . web_add_handler_done ) {
callBerryEventDispatcher ( PSTR ( " web_add_handler " ) , nullptr , 0 , nullptr ) ;
berry . web_add_handler_done = true ;
}
2024-01-08 08:30:18 +00:00
WebServer_on ( " /bc " , HandleBerryConsole ) ;
WebServer_on ( " /tapp " , HandleBerryBECLoader , HTTP_GET ) ;
2021-02-28 19:50:37 +00:00
break ;
# endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART :
2021-03-20 17:44:35 +00:00
callBerryEventDispatcher ( PSTR ( " save_before_restart " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
case FUNC_WEB_SENSOR :
2021-03-20 17:44:35 +00:00
callBerryEventDispatcher ( PSTR ( " web_sensor " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
2023-03-12 18:01:08 +00:00
case FUNC_WEB_GET_ARG :
callBerryEventDispatcher ( PSTR ( " web_get_arg " ) , nullptr , 0 , nullptr ) ;
break ;
2021-02-28 19:50:37 +00:00
case FUNC_JSON_APPEND :
2021-03-27 18:02:22 +00:00
callBerryEventDispatcher ( PSTR ( " json_append " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
case FUNC_BUTTON_PRESSED :
2021-03-20 17:44:35 +00:00
callBerryEventDispatcher ( PSTR ( " button_pressed " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
2023-12-27 21:03:56 +00:00
case FUNC_ACTIVE :
result = true ;
break ;
2021-02-28 19:50:37 +00:00
}
return result ;
}
# endif // USE_BERRY