Tasmota/tasmota/xdrv_52_9_berry.ino

803 lines
27 KiB
Arduino
Raw Normal View History

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>
#include "be_vm.h"
2021-02-28 19:50:37 +00:00
extern "C" {
extern void be_load_custom_libs(bvm *vm);
}
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
};
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-02-28 19:50:37 +00:00
\*********************************************************************************************/
// // call a function (if exists) of type void -> void
bool callBerryRule(void) {
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-06-02 16:56:44 +01:00
serviced = callBerryEventDispatcher(PSTR("rule"), nullptr, 0, 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));
} else {
AddLog(LOG_LEVEL_ERROR, PSTR(D_LOG_BERRY "Exception> '%s' - %s"), be_tostring(berry.vm, -2), be_tostring(berry.vm, -1));
}
} 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
// 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
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-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);
}
break;
default: break;
}
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;
do {
2021-02-28 19:50:37 +00:00
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);
comp_set_named_gbl(berry.vm);
be_load_custom_libs(berry.vm);
2021-02-28 19:50:37 +00:00
// Register functions
// be_regfunc(berry.vm, PSTR("log"), l_logInfo);
// be_regfunc(berry.vm, PSTR("save"), l_save);
2021-02-28 19:50:37 +00:00
// 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) {
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);
if (be_pcall(berry.vm, 1) != 0) {
BerryDumpErrorAndClear(berry.vm, false);
return;
}
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) {
AddLog(LOG_LEVEL_INFO, D_LOG_BERRY "sucessfully loaded '%s'", script_name);
} 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;
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();
}
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-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
ret_code = be_pcall(berry.vm, 0); // execute code
// AddLog(LOG_LEVEL_INFO, PSTR(">>>> be_pcall ret=%i"), ret_code);
if (0 == ret_code) {
if (!be_isnil(berry.vm, 1)) {
const char * ret_val = be_tostring(berry.vm, 1);
berry.log.addString(ret_val, nullptr, "\n");
// AddLog(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-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
"});"
"}"
"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
// 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);
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
// // 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:
result = callBerryRule();
2021-02-28 19:50:37 +00:00
break;
2021-03-20 17:44:35 +00:00
case FUNC_MQTT_DATA:
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-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
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-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:
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