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>
2022-01-05 08:50:38 +00:00
# include "berry_tasmota.h"
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
2021-05-25 17:53:10 +01:00
D_CMND_BR_RUN
2021-02-28 19:50:37 +00:00
;
void ( * const BerryCommand [ ] ) ( void ) PROGMEM = {
2021-05-25 17:53:10 +01:00
CmndBrRun ,
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 " {
void * berry_malloc ( uint32_t size ) ;
void * berry_realloc ( void * ptr , size_t size ) ;
# ifdef USE_BERRY_PSRAM
void * berry_malloc ( uint32_t size ) {
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
void * berry_malloc ( uint32_t size ) {
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
void * berry_malloc32 ( uint32_t size ) {
# 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 ) {
2021-02-28 19:50:37 +00:00
if ( berry . rules_busy ) { return false ; }
berry . rules_busy = true ;
2021-06-02 16:56:44 +01:00
char * json_event = XdrvMailbox . data ;
2021-02-28 19:50:37 +00:00
bool serviced = false ;
2021-10-19 21:38:54 +01:00
serviced = callBerryEventDispatcher ( teleperiod ? " tele " : " rule " , nullptr , 0 , event ? event : XdrvMailbox . data ) ;
2021-02-28 19:50:37 +00:00
berry . rules_busy = false ;
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()`
void callBerryFastLoop ( void ) {
bvm * vm = berry . vm ;
if ( nullptr = = vm ) { return ; }
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 ) ;
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 ;
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
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
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 ) {
be_vm_delete ( berry . vm ) ;
berry . vm = nullptr ;
}
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 */
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
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
2022-01-13 18:20:10 +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 ;
}
}
}
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
int32_t ret_code1 , ret_code2 ;
bool berry_init_ok = false ;
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 ;
const char * ret_type , * ret_val ;
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-03-09 09:32:47 +00:00
" t.scrollTop=99999999; "
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(); "
" x.onreadystatechange=function(){ "
" 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-03-09 09:32:47 +00:00
" t.scrollTop=99999999; "
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(); "
" 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
" }); "
" } "
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> "
// "<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 =
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 ;
}
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 ( ) ;
}
# endif // USE_WEBSERVER
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv52 ( uint8_t function )
{
bool result = false ;
switch ( function ) {
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 ;
}
2022-01-09 18:22:23 +00:00
if ( TasmotaGlobal . berry_fast_loop_enabled ) { // call only if enabled at global level
callBerryFastLoop ( ) ; // call `tasmota.fast_loop()` optimized for minimal performance impact
}
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_200_MSECOND :
callBerryEventDispatcher ( PSTR ( " every_200ms " ) , nullptr , 0 , nullptr ) ;
break ;
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 ) ;
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 :
2021-05-28 21:37:06 +01:00
callBerryEventDispatcher ( PSTR ( " web_add_handler " ) , nullptr , 0 , nullptr ) ;
2021-06-11 16:09:03 +01:00
WebServer_on ( PSTR ( " /bc " ) , HandleBerryConsole ) ;
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 ;
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 ;
}
return result ;
}
# endif // USE_BERRY