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>
2021-07-27 20:39:24 +01:00
# include "be_vm.h"
2021-11-10 18:31:22 +00:00
# include "ZipReadFS.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
AddLog ( LOG_LEVEL_ERROR , D_LOG_BERRY " Error be_top is non zero=%d " , top ) ;
}
}
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
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-06-12 10:12:57 +01:00
void BerryDumpErrorAndClear ( bvm * vm , bool berry_console ) ;
void BerryDumpErrorAndClear ( bvm * vm , bool berry_console ) {
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 ) ) {
if ( berry_console ) {
berry_log_C ( PSTR ( D_LOG_BERRY " Exception> '%s' - %s " ) , be_tostring ( berry . vm , - 2 ) , be_tostring ( berry . vm , - 1 ) ) ;
2021-09-27 12:39:12 +01:00
be_tracestack ( vm ) ;
top = be_top ( vm ) ; // update top after dump
2021-06-12 10:12:57 +01:00
} else {
AddLog ( LOG_LEVEL_ERROR , PSTR ( D_LOG_BERRY " Exception> '%s' - %s " ) , be_tostring ( berry . vm , - 2 ) , be_tostring ( berry . vm , - 1 ) ) ;
2021-10-14 08:15:19 +01:00
be_tracestack ( vm ) ;
2021-06-12 10:12:57 +01:00
}
} else {
be_dumpstack ( vm ) ;
}
be_pop ( vm , top ) ;
}
2021-02-28 19:50:37 +00:00
// void callBerryMqttData(void) {
// AddLog(LOG_LEVEL_INFO, D_LOG_BERRY "callBerryMqttData");
// if (nullptr == berry.vm) { return; }
// if (XdrvMailbox.data_len < 1) {
// return;
// }
// const char * topic = XdrvMailbox.topic;
// const char * payload = XdrvMailbox.data;
// checkBeTop();
// be_getglobal(berry.vm, "mqtt_data_dispatch");
// if (!be_isnil(berry.vm, -1)) {
// be_pushstring(berry.vm, topic);
// be_pushstring(berry.vm, payload);
// be_pcall(berry.vm, 0);
// be_pop(berry.vm, 3); // remove function object
// } else {
// be_pop(berry.vm, 1); // remove nil object
// }
// checkBeTop();
// }
2021-03-20 17:44:35 +00:00
/*
// 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_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
}
// 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
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 ) ;
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 ) {
BerryDumpErrorAndClear ( vm , false ) ; // log in Tasmota console only
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
}
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 ) {
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 ;
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_BERRY " GC from %i to %i bytes (in %d ms) " ) , vm_usage , vm_usage2 , gc_elapsed ) ;
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 ) ;
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_BERRY " Stack resized from %i to %i bytes " ) , stack_before , stack_after ) ;
}
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-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` */
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-06-12 10:12:57 +01:00
BerryDumpErrorAndClear ( berry . vm , false ) ;
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-06-12 10:12:57 +01:00
BerryDumpErrorAndClear ( berry . vm , false ) ;
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 ) {
BerryDumpErrorAndClear ( berry . vm , false ) ;
} else {
be_pop ( berry . vm , 1 ) ;
2021-04-03 18:53:52 +01:00
}
2021-02-28 19:50:37 +00:00
2021-06-12 10:12:57 +01:00
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_BERRY " Berry initialized, RAM used=%u " ) , callBerryGC ( ) ) ;
2021-02-28 19:50:37 +00:00
berry_init_ok = true ;
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 ) {
BerryDumpErrorAndClear ( berry . vm , false ) ;
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 ) {
2021-08-23 18:47:37 +01:00
AddLog ( LOG_LEVEL_INFO , D_LOG_BERRY " successfully loaded '%s' " , script_name ) ;
2021-06-12 10:12:57 +01:00
} else {
AddLog ( LOG_LEVEL_INFO , D_LOG_BERRY " no '%s' " , script_name ) ;
}
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 ) {
2021-03-03 07:34:38 +00:00
// AddLog(LOG_LEVEL_INFO, "run: top=%d", be_top(berry.vm));
// AddLog(LOG_LEVEL_INFO, "run: type(1)=%s", be_typename(berry.vm, 1));
// AddLog(LOG_LEVEL_INFO, "run: type(2)=%s", be_typename(berry.vm, 2));
2021-02-28 19:50:37 +00:00
// 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-06-12 10:12:57 +01:00
BerryDumpErrorAndClear ( berry . vm , true ) ;
// 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(LOG_LEVEL_INFO, PSTR(">>> %s"), exception_s);
// be_pop(berry.vm, 2);
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=''; "
" 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....
" } "
" }; "
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 ) {
_WSContentSend ( ( char * ) l ) ;
}
berry . log . reset ( ) ;
}
WSContentEnd ( ) ;
}
void HandleBerryConsole ( void )
{
if ( ! HttpCheckPriviledgedAccess ( ) ) { return ; }
// int i=16;
// // AddLog(LOG_LEVEL_INFO, PSTR("Size = %d %d"), sizeof(LList_elt<char[12]>), sizeof(LList_elt<char[0]>)+12);
// LList_elt<char[0]> * elt = (LList_elt<char[0]>*) ::operator new(sizeof(LList_elt<char[0]>) + 12);
if ( Webserver - > hasArg ( F ( " c2 " ) ) ) { // Console refresh requested
HandleBerryConsoleRefresh ( ) ;
return ;
}
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_HTTP " Berry " D_CONSOLE ) ) ;
WSContentStart_P ( PSTR ( " Berry " D_CONSOLE ) ) ;
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 ( ) ;
}
// void HandleBerryConsoleRefresh(void)
// {
// String svalue = Webserver->arg(F("c1"));
// if (svalue.length() && (svalue.length() < MQTT_MAX_PACKET_SIZE)) {
// // TODO run command and store result
2021-06-05 10:47:09 +01:00
// // AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_COMMAND "%s"), svalue.c_str());
2021-03-20 17:44:35 +00:00
// // 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;
2021-06-11 17:14:12 +01:00
// while (GetLog(Settings->weblog_level, &index, &line, &len)) {
2021-05-31 15:17:45 +01:00
// if (cflg) { WSContentSend_P(PSTR("\n")); }
// WSContentFlush();
// Webserver->sendContent(line, len -1);
2021-03-20 17:44:35 +00:00
// cflg = true;
// }
// WSContentSend_P(PSTR("}1"));
// WSContentEnd();
// }
# endif // USE_WEBSERVER
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv52 ( uint8_t function )
{
bool result = false ;
switch ( function ) {
2021-05-25 17:53:10 +01:00
// case FUNC_PRE_INIT: // we start Berry in pre_init so that other modules can call Berry in their init methods
// // case FUNC_INIT:
// BerryInit();
// break;
2021-03-13 21:42:24 +00:00
case FUNC_LOOP :
if ( ! berry . autoexec_done ) {
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 ;
}
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 ) ;
2021-02-28 19:50:37 +00:00
break ;
2021-03-20 17:44:35 +00:00
case FUNC_EVERY_50_MSECOND :
callBerryEventDispatcher ( PSTR ( " every_50ms " ) , nullptr , 0 , nullptr ) ;
2021-02-28 19:50:37 +00:00
break ;
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
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 ;
2021-02-28 19:50:37 +00:00
// case FUNC_SET_POWER:
// break;
# 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