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>
const char kBrCommands [ ] PROGMEM = D_PRFX_BR " | " // prefix
D_CMND_BR_RUN " | " D_CMND_BR_RESET
;
void ( * const BerryCommand [ ] ) ( void ) PROGMEM = {
CmndBrRun , CmndBrReset ,
} ;
class BerrySupport {
public :
bvm * vm = nullptr ; // berry vm
bool rules_busy = false ; // are we already processing rules, avoid infinite loop
2021-03-13 21:42:24 +00:00
bool autoexec_done = false ; // do we still need to load 'autoexec.be'
2021-02-28 19:50:37 +00:00
} ;
BerrySupport berry ;
//
// 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 ) ;
}
}
/*********************************************************************************************\
* Handlers for Berry calls and async
*
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// // call a function (if exists) of type void -> void
bool callBerryRule ( void ) {
if ( berry . rules_busy ) { return false ; }
berry . rules_busy = true ;
char * json_event = TasmotaGlobal . mqtt_data ;
bool serviced = false ;
checkBeTop ( ) ;
be_getglobal ( berry . vm , " _exec_rules " ) ;
if ( ! be_isnil ( berry . vm , - 1 ) ) {
// {
// String event_saved = TasmotaGlobal.mqtt_data;
// // json_event = {"INA219":{"Voltage":4.494,"Current":0.020,"Power":0.089}}
// // json_event = {"System":{"Boot":1}}
// // json_event = {"SerialReceived":"on"} - invalid but will be expanded to {"SerialReceived":{"Data":"on"}}
// char *p = strchr(json_event, ':');
// if ((p != NULL) && !(strchr(++p, ':'))) { // Find second colon
// event_saved.replace(F(":"), F(":{\"Data\":"));
// event_saved += F("}");
// // event_saved = {"SerialReceived":{"Data":"on"}}
// }
// be_pushstring(berry.vm, event_saved.c_str());
// }
be_pushstring ( berry . vm , TasmotaGlobal . mqtt_data ) ;
int ret = be_pcall ( berry . vm , 1 ) ;
serviced = be_tobool ( berry . vm , 1 ) ;
AddLog ( LOG_LEVEL_DEBUG , PSTR ( D_LOG_BERRY " Event (%s) serviced=%d " ) , TasmotaGlobal . mqtt_data , serviced ) ;
be_pop ( berry . vm , 2 ) ; // remove function object
} else {
be_pop ( berry . vm , 1 ) ; // remove nil object
}
checkBeTop ( ) ;
berry . rules_busy = false ;
return serviced ; // TODO event not handled
}
bool callBerryCommand ( void ) {
2021-03-03 07:34:38 +00:00
bool serviced = false ;
2021-02-28 19:50:37 +00:00
checkBeTop ( ) ;
be_getglobal ( berry . vm , " _exec_cmd " ) ;
if ( ! be_isnil ( berry . vm , - 1 ) ) {
be_pushstring ( berry . vm , XdrvMailbox . topic ) ;
be_pushint ( berry . vm , XdrvMailbox . index ) ;
be_pushstring ( berry . vm , XdrvMailbox . data ) ;
int ret = be_pcall ( berry . vm , 3 ) ;
2021-03-03 07:34:38 +00:00
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: top=%d", be_top(berry.vm));
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(1)=%s", be_typename(berry.vm, 1));
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(2)=%s", be_typename(berry.vm, 2));
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(3)=%s", be_typename(berry.vm, 3));
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(4)=%s", be_typename(berry.vm, 4));
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: type(5)=%s", be_typename(berry.vm, 5));
serviced = be_tobool ( berry . vm , 1 ) ; // return value is in slot 1
// AddLog(LOG_LEVEL_INFO, "callBerryCommand: serviced=%d", serviced);
2021-02-28 19:50:37 +00:00
be_pop ( berry . vm , 4 ) ; // remove function object
} else {
be_pop ( berry . vm , 1 ) ; // remove nil object
}
checkBeTop ( ) ;
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 ) {
size_t ram_used = 0 ;
checkBeTop ( ) ;
be_getglobal ( berry . vm , " _gc " ) ;
if ( ! be_isnil ( berry . vm , - 1 ) ) {
int ret = be_pcall ( berry . vm , 0 ) ;
ram_used = be_toint ( berry . vm , 1 ) ;
be_pop ( berry . vm , 1 ) ; // remove function object
} else {
be_pop ( berry . vm , 1 ) ; // remove nil object
}
checkBeTop ( ) ;
return ram_used ;
}
// 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();
// }
// call a function (if exists) of type void -> void
void callBerryFunctionVoid ( const char * fname ) {
if ( nullptr = = berry . vm ) { return ; }
checkBeTop ( ) ;
be_getglobal ( berry . vm , fname ) ;
if ( ! be_isnil ( berry . vm , - 1 ) ) {
be_pcall ( berry . vm , 0 ) ;
}
be_pop ( berry . vm , 1 ) ; // remove function or nil object
checkBeTop ( ) ;
}
2021-03-03 07:34:38 +00:00
/*********************************************************************************************\
* VM Observability
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void BerryObservability ( bvm * vm , int32_t event . . . ) ;
void BerryObservability ( bvm * vm , int32_t event . . . ) {
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 ) ;
}
break ;
default : break ;
}
va_end ( param ) ;
}
2021-02-28 19:50:37 +00:00
/*********************************************************************************************\
* VM Init
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
extern " C " {
extern size_t be_gc_memcount ( bvm * vm ) ;
extern void be_gc_collect ( bvm * vm ) ;
}
void BrReset ( void ) {
// 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 ;
do {
berry . vm = be_vm_new ( ) ; /* create a virtual machine instance */
2021-03-03 07:34:38 +00:00
be_set_obs_hook ( berry . vm , & BerryObservability ) ;
2021-02-28 19:50:37 +00:00
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Berry VM created, RAM used=%u"), be_gc_memcount(berry.vm));
// Register functions
be_regfunc ( berry . vm , PSTR ( " log " ) , l_logInfo ) ;
be_regfunc ( berry . vm , PSTR ( " save " ) , l_save ) ;
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Berry function registered, RAM used=%u"), be_gc_memcount(berry.vm));
ret_code1 = be_loadstring ( berry . vm , berry_prog ) ;
if ( ret_code1 ! = 0 ) {
AddLog ( LOG_LEVEL_ERROR , PSTR ( D_LOG_BERRY " ERROR: be_loadstring [%s] %s " ) , be_tostring ( berry . vm , - 2 ) , be_tostring ( berry . vm , - 1 ) ) ;
be_pop ( berry . vm , 2 ) ;
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 ) {
AddLog ( LOG_LEVEL_ERROR , PSTR ( D_LOG_BERRY " ERROR: be_pcall [%s] %s " ) , be_tostring ( berry . vm , - 2 ) , be_tostring ( berry . vm , - 1 ) ) ;
2021-03-13 21:42:24 +00:00
be_pop ( berry . vm , 1 ) ;
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));
be_pop ( berry . vm , 1 ) ;
be_gc_collect ( berry . vm ) ;
AddLog ( LOG_LEVEL_INFO , PSTR ( D_LOG_BERRY " Berry initialized, RAM used=%u " ) , callBerryGC ( ) ) ;
// AddLog(LOG_LEVEL_INFO, PSTR("Delete Berry VM"));
// be_vm_delete(vm);
// AddLog(LOG_LEVEL_INFO, PSTR("After Berry"));
berry_init_ok = true ;
} while ( 0 ) ;
if ( ! berry_init_ok ) {
// free resources
if ( berry . vm ! = nullptr ) {
be_vm_delete ( berry . vm ) ;
berry . vm = nullptr ;
}
}
}
2021-03-13 21:42:24 +00:00
void BrAutoexec ( void ) {
if ( berry . vm = = nullptr ) { return ; }
int32_t ret_code1 , ret_code2 ;
bool berry_init_ok = false ;
// load 'autoexec.be' or 'autoexec.bec'
ret_code1 = be_loadstring ( berry . vm , berry_autoexec ) ;
// be_dumpstack(berry.vm);
if ( ret_code1 ! = 0 ) {
be_pop ( berry . vm , 2 ) ;
return ;
}
ret_code2 = be_pcall ( berry . vm , 0 ) ;
// be_dumpstack(berry.vm);
if ( ret_code1 ! = 0 ) {
// AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_BERRY "ERROR: be_pcall [%s] %s"), be_tostring(berry.vm, -2), be_tostring(berry.vm, -1));
be_pop ( berry . vm , 1 ) ;
return ;
}
// AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_BERRY "Berry code ran, RAM used=%u"), be_gc_memcount(berry.vm));
be_pop ( berry . vm , 1 ) ;
// be_dumpstack(berry.vm);
}
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 ;
ret_code = be_pcall ( berry . vm , 0 ) ; // execute code
} 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 ( ) ;
}
//
// Command `BrReset`
//
void CmndBrReset ( void ) {
if ( berry . vm = = nullptr ) { ResponseCmndChar_P ( PSTR ( D_BR_NOT_STARTED ) ) ; return ; }
BrReset ( ) ;
}
/*********************************************************************************************\
* Interface
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
bool Xdrv52 ( uint8_t function )
{
bool result = false ;
switch ( function ) {
//case FUNC_PRE_INIT:
case FUNC_INIT :
BrReset ( ) ;
break ;
2021-03-13 21:42:24 +00:00
case FUNC_LOOP :
if ( ! berry . autoexec_done ) {
BrAutoexec ( ) ;
berry . autoexec_done = true ;
}
break ;
2021-02-28 19:50:37 +00:00
case FUNC_EVERY_50_MSECOND :
callBerryFunctionVoid ( PSTR ( " _run_deferred " ) ) ;
break ;
case FUNC_EVERY_100_MSECOND :
callBerryFunctionVoid ( PSTR ( " every_100ms " ) ) ;
break ;
case FUNC_EVERY_SECOND :
callBerryFunctionVoid ( PSTR ( " every_second " ) ) ;
break ;
case FUNC_COMMAND :
result = DecodeCommand ( kBrCommands , BerryCommand ) ;
if ( ! result ) {
result = callBerryCommand ( ) ;
}
break ;
// case FUNC_SET_POWER:
// break;
case FUNC_RULES_PROCESS :
result = callBerryRule ( ) ;
break ;
# ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON :
break ;
case FUNC_WEB_ADD_MAIN_BUTTON :
break ;
case FUNC_WEB_ADD_HANDLER :
break ;
# endif // USE_WEBSERVER
case FUNC_SAVE_BEFORE_RESTART :
break ;
case FUNC_MQTT_DATA :
// callBerryMqttData();
break ;
case FUNC_WEB_SENSOR :
break ;
case FUNC_JSON_APPEND :
break ;
case FUNC_BUTTON_PRESSED :
break ;
}
return result ;
}
# endif // USE_BERRY