mirror of https://github.com/arendst/Tasmota.git
`BrRestart` now supports web handlers to work after Berry restart (#19057)
This commit is contained in:
parent
862edddb56
commit
57c4825ccd
|
@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Fixed
|
### Fixed
|
||||||
- Berry various fixes for Walrus Operator (#18982)
|
- Berry various fixes for Walrus Operator (#18982)
|
||||||
- MiElHVAC power commands regression from v12.4.0.1 (#18923)
|
- MiElHVAC power commands regression from v12.4.0.1 (#18923)
|
||||||
|
- `BrRestart` now supports web handlers to work after Berry restart
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Support for ESP32-C3 with chip rev below 3 (old development boards)
|
- Support for ESP32-C3 with chip rev below 3 (old development boards)
|
||||||
|
|
|
@ -250,7 +250,6 @@ static int32_t call_berry_cb(int32_t num, int32_t v0, int32_t v1, int32_t v2, in
|
||||||
|
|
||||||
bvm * vm = be_cb_hooks[num].vm;
|
bvm * vm = be_cb_hooks[num].vm;
|
||||||
bvalue *f = &be_cb_hooks[num].f;
|
bvalue *f = &be_cb_hooks[num].f;
|
||||||
if (vm == NULL) { return 0; } // function is not alive anymore, don't crash
|
|
||||||
|
|
||||||
// push function (don't check type)
|
// push function (don't check type)
|
||||||
bvalue *top = be_incrtop(vm);
|
bvalue *top = be_incrtop(vm);
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
|
|
||||||
#ifdef USE_WEBSERVER
|
#ifdef USE_WEBSERVER
|
||||||
|
|
||||||
|
#include "be_exec.h"
|
||||||
|
#include "be_vm.h"
|
||||||
|
|
||||||
extern int w_webserver_member(bvm *vm);
|
extern int w_webserver_member(bvm *vm);
|
||||||
extern int w_webserver_on(bvm *vm);
|
extern int w_webserver_on(bvm *vm);
|
||||||
extern int w_webserver_state(bvm *vm);
|
extern int w_webserver_state(bvm *vm);
|
||||||
|
@ -32,6 +35,107 @@ extern int w_webserver_arg_name(bvm *vm);
|
||||||
extern int w_webserver_has_arg(bvm *vm);
|
extern int w_webserver_has_arg(bvm *vm);
|
||||||
|
|
||||||
|
|
||||||
|
// To allow a full restart of the Berry VM, we need to supplement the webserver Request Handler
|
||||||
|
// model from Arduino framework.
|
||||||
|
// We use our own list of callbacks
|
||||||
|
|
||||||
|
#define WEBSERVER_REQ_HANDLER_HOOK_MAX 16 // max number of callbacks, each callback requires a distinct address
|
||||||
|
typedef struct be_webserver_callback_hook_t {
|
||||||
|
bvm *vm; // make sure we are using the same VM
|
||||||
|
bvalue f; // the Berry function to call
|
||||||
|
} be_webserver_callback_hook_t;
|
||||||
|
|
||||||
|
static be_webserver_callback_hook_t be_webserver_cb_hooks[WEBSERVER_REQ_HANDLER_HOOK_MAX];
|
||||||
|
|
||||||
|
static void be_call_webserver_hook_cb(int32_t num);
|
||||||
|
typedef void (*berry_webserver_cb_t)(void);
|
||||||
|
#define WEBSERVER_HOOK_CB(n) void berry_webserver_cb_##n(void) { be_call_webserver_hook_cb(n); }
|
||||||
|
// list the callbacks
|
||||||
|
WEBSERVER_HOOK_CB(0);
|
||||||
|
WEBSERVER_HOOK_CB(1);
|
||||||
|
WEBSERVER_HOOK_CB(2);
|
||||||
|
WEBSERVER_HOOK_CB(3);
|
||||||
|
WEBSERVER_HOOK_CB(4);
|
||||||
|
WEBSERVER_HOOK_CB(5);
|
||||||
|
WEBSERVER_HOOK_CB(6);
|
||||||
|
WEBSERVER_HOOK_CB(7);
|
||||||
|
WEBSERVER_HOOK_CB(8);
|
||||||
|
WEBSERVER_HOOK_CB(9);
|
||||||
|
WEBSERVER_HOOK_CB(10);
|
||||||
|
WEBSERVER_HOOK_CB(11);
|
||||||
|
WEBSERVER_HOOK_CB(12);
|
||||||
|
WEBSERVER_HOOK_CB(13);
|
||||||
|
WEBSERVER_HOOK_CB(14);
|
||||||
|
WEBSERVER_HOOK_CB(15);
|
||||||
|
|
||||||
|
// array of callbacks
|
||||||
|
static const berry_webserver_cb_t berry_callback_array[WEBSERVER_REQ_HANDLER_HOOK_MAX] = {
|
||||||
|
berry_webserver_cb_0,
|
||||||
|
berry_webserver_cb_1,
|
||||||
|
berry_webserver_cb_2,
|
||||||
|
berry_webserver_cb_3,
|
||||||
|
berry_webserver_cb_4,
|
||||||
|
berry_webserver_cb_5,
|
||||||
|
berry_webserver_cb_6,
|
||||||
|
berry_webserver_cb_7,
|
||||||
|
berry_webserver_cb_8,
|
||||||
|
berry_webserver_cb_9,
|
||||||
|
berry_webserver_cb_10,
|
||||||
|
berry_webserver_cb_11,
|
||||||
|
berry_webserver_cb_12,
|
||||||
|
berry_webserver_cb_13,
|
||||||
|
berry_webserver_cb_14,
|
||||||
|
berry_webserver_cb_15,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return slot number
|
||||||
|
// -1 if no more available
|
||||||
|
berry_webserver_cb_t be_webserver_allocate_hook(bvm *vm, int32_t slot, bvalue *f) {
|
||||||
|
if (slot < 0 || slot >= WEBSERVER_REQ_HANDLER_HOOK_MAX) return NULL; // invalid call, avoid a crash
|
||||||
|
be_webserver_cb_hooks[slot].vm = vm;
|
||||||
|
be_webserver_cb_hooks[slot].f = *f;
|
||||||
|
return berry_callback_array[slot];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* `be_webserver_cb_deinit`:
|
||||||
|
* Clean any callback for this VM
|
||||||
|
\*********************************************************************************************/
|
||||||
|
void be_webserver_cb_deinit(bvm *vm) {
|
||||||
|
for (int32_t i = 0; i < WEBSERVER_REQ_HANDLER_HOOK_MAX; i++) {
|
||||||
|
if (be_webserver_cb_hooks[i].vm == vm) {
|
||||||
|
be_webserver_cb_hooks[i].vm = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************************************************************\
|
||||||
|
* Callback structures
|
||||||
|
*
|
||||||
|
\*********************************************************************************************/
|
||||||
|
void be_call_webserver_hook_cb(int32_t num) {
|
||||||
|
// call berry cb dispatcher
|
||||||
|
int32_t ret = 0;
|
||||||
|
// retrieve vm and function
|
||||||
|
if (num < 0 || num >= WEBSERVER_REQ_HANDLER_HOOK_MAX || be_webserver_cb_hooks[num].vm == NULL) return; // invalid call, avoid a crash
|
||||||
|
|
||||||
|
bvm * vm = be_webserver_cb_hooks[num].vm;
|
||||||
|
bvalue *f = &be_webserver_cb_hooks[num].f;
|
||||||
|
|
||||||
|
// push function (don't check type)
|
||||||
|
bvalue *top = be_incrtop(vm);
|
||||||
|
*top = *f;
|
||||||
|
|
||||||
|
ret = be_pcall(vm, 0); // 4 arguments
|
||||||
|
if (ret != 0) {
|
||||||
|
if (vm->obshook != NULL) (*vm->obshook)(vm, BE_OBS_PCALL_ERROR);
|
||||||
|
be_pop(vm, be_top(vm)); // clear Berry stack
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
be_pop(vm, 1); // remove result
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* @const_object_info_begin
|
/* @const_object_info_begin
|
||||||
module webserver (scope: global) {
|
module webserver (scope: global) {
|
||||||
member, func(w_webserver_member)
|
member, func(w_webserver_member)
|
||||||
|
|
|
@ -92,6 +92,7 @@ public:
|
||||||
bvm *vm = nullptr; // berry vm
|
bvm *vm = nullptr; // berry vm
|
||||||
int32_t timeout = 0; // Berry heartbeat timeout, preventing code to run for too long. `0` means not enabled
|
int32_t timeout = 0; // Berry heartbeat timeout, preventing code to run for too long. `0` means not enabled
|
||||||
bool rules_busy = false; // are we already processing rules, avoid infinite loop
|
bool rules_busy = false; // are we already processing rules, avoid infinite loop
|
||||||
|
bool web_add_handler_done = false; // did we already sent `web_add_handler` event
|
||||||
bool autoexec_done = false; // do we still need to load 'autoexec.be'
|
bool autoexec_done = false; // do we still need to load 'autoexec.be'
|
||||||
bool repl_active = false; // is REPL running (activates log recording)
|
bool repl_active = false; // is REPL running (activates log recording)
|
||||||
// output log is stored as a LinkedList of buffers
|
// output log is stored as a LinkedList of buffers
|
||||||
|
|
|
@ -70,7 +70,14 @@ extern "C" {
|
||||||
* import webserver
|
* import webserver
|
||||||
*
|
*
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
|
||||||
|
#define WEBSERVER_REQ_HANDLER_HOOK_MAX 16 // max number of callbacks, each callback requires a distinct address
|
||||||
|
static String be_webserver_prefix[WEBSERVER_REQ_HANDLER_HOOK_MAX];
|
||||||
|
static uint8_t be_webserver_method[WEBSERVER_REQ_HANDLER_HOOK_MAX];
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
typedef void (*berry_webserver_cb_t)(void);
|
||||||
|
extern berry_webserver_cb_t be_webserver_allocate_hook(bvm *vm, int32_t num, bvalue *f);
|
||||||
// Berry: `webserver.on(prefix:string, callback:closure) -> nil`
|
// Berry: `webserver.on(prefix:string, callback:closure) -> nil`
|
||||||
//
|
//
|
||||||
// WARNING - this should be called only when receiving `web_add_handler` event.
|
// WARNING - this should be called only when receiving `web_add_handler` event.
|
||||||
|
@ -88,27 +95,40 @@ extern "C" {
|
||||||
method = be_toint(vm, 3);
|
method = be_toint(vm, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
be_getglobal(vm, PSTR("tasmota"));
|
// find if the prefix/method is already defined
|
||||||
if (!be_isnil(vm, -1)) {
|
int32_t slot;
|
||||||
be_getmethod(vm, -1, PSTR("gen_cb"));
|
for (slot = 0; slot < WEBSERVER_REQ_HANDLER_HOOK_MAX; slot++) {
|
||||||
if (!be_isnil(vm, -1)) {
|
// AddLog(LOG_LEVEL_INFO, ">>>: slot [%i] prefix='%s' method=%i", slot, be_webserver_prefix[slot] ? be_webserver_prefix[slot].c_str() : "<empty>", be_webserver_method[slot]);
|
||||||
be_pushvalue(vm, -2); // add instance as first arg
|
if (be_webserver_prefix[slot] == prefix && be_webserver_method[slot] == method) {
|
||||||
be_pushvalue(vm, 2); // push closure as second arg
|
break;
|
||||||
be_pcall(vm, 2); // 2 arguments
|
}
|
||||||
be_pop(vm, 2);
|
}
|
||||||
|
|
||||||
if (be_iscomptr(vm, -1)) { // sanity check
|
if (slot >= WEBSERVER_REQ_HANDLER_HOOK_MAX) {
|
||||||
const void * cb = be_tocomptr(vm, -1);
|
// we didn't find a duplicate, let's find a free slot
|
||||||
// All good, we can proceed
|
for (slot = 0; slot < WEBSERVER_REQ_HANDLER_HOOK_MAX; slot++) {
|
||||||
|
// AddLog(LOG_LEVEL_INFO, ">>>2: slot [%i] prefix='%s' method=%i", slot, be_webserver_prefix[slot] ? be_webserver_prefix[slot].c_str() : "<empty>", be_webserver_method[slot]);
|
||||||
WebServer_on(prefix, (void (*)()) cb, method);
|
if (be_webserver_prefix[slot].equals("")) {
|
||||||
be_return_nil(vm); // return, all good
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
be_pop(vm, 1);
|
if (slot >= WEBSERVER_REQ_HANDLER_HOOK_MAX) {
|
||||||
|
be_raise(vm, "internal_error", "no more slots for webserver hooks");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// be_pop(vm, 1); // not really useful since we raise an exception anyways
|
// AddLog(LOG_LEVEL_INFO, ">>>: slot found = %i", slot);
|
||||||
be_raise(vm, kInternalError, nullptr);
|
|
||||||
|
bvalue *v = be_indexof(vm, 2);
|
||||||
|
if (be_isgcobj(v)) {
|
||||||
|
be_gc_fix_set(vm, v->v.gc, btrue); // mark the function as non-gc
|
||||||
|
}
|
||||||
|
berry_webserver_cb_t cb = be_webserver_allocate_hook(vm, slot, v);
|
||||||
|
if (cb == NULL) { be_raise(vm, kInternalError, nullptr); }
|
||||||
|
be_webserver_prefix[slot] = prefix;
|
||||||
|
be_webserver_method[slot] = method;
|
||||||
|
|
||||||
|
WebServer_on(prefix, cb, method);
|
||||||
|
be_return_nil(vm); // return, all good
|
||||||
}
|
}
|
||||||
be_raise(vm, kTypeError, nullptr);
|
be_raise(vm, kTypeError, nullptr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,12 +304,17 @@ void BrShowState(void) {
|
||||||
/*********************************************************************************************\
|
/*********************************************************************************************\
|
||||||
* VM Init
|
* VM Init
|
||||||
\*********************************************************************************************/
|
\*********************************************************************************************/
|
||||||
|
extern "C" void be_webserver_cb_deinit(bvm *vm);
|
||||||
void BerryInit(void) {
|
void BerryInit(void) {
|
||||||
// clean previous VM if any
|
// clean previous VM if any
|
||||||
if (berry.vm != nullptr) {
|
if (berry.vm != nullptr) {
|
||||||
be_cb_deinit(berry.vm); // deregister any C callback for this VM
|
be_cb_deinit(berry.vm); // deregister any C callback for this VM
|
||||||
|
#ifdef USE_WEBSERVER
|
||||||
|
be_webserver_cb_deinit(berry.vm); // deregister C callbacks managed by webserver
|
||||||
|
#endif // USE_WEBSERVER
|
||||||
be_vm_delete(berry.vm);
|
be_vm_delete(berry.vm);
|
||||||
berry.vm = nullptr;
|
berry.vm = nullptr;
|
||||||
|
berry.web_add_handler_done = false;
|
||||||
berry.autoexec_done = false;
|
berry.autoexec_done = false;
|
||||||
berry.repl_active = false;
|
berry.repl_active = false;
|
||||||
berry.rules_busy = false;
|
berry.rules_busy = false;
|
||||||
|
@ -760,6 +765,18 @@ bool Xdrv52(uint32_t function)
|
||||||
|
|
||||||
BrLoad("autoexec.be"); // run autoexec.be at first tick, so we know all modules are initialized
|
BrLoad("autoexec.be"); // run autoexec.be at first tick, so we know all modules are initialized
|
||||||
berry.autoexec_done = true;
|
berry.autoexec_done = true;
|
||||||
|
|
||||||
|
// check if `web_add_handler` was missed, for example because of Berry VM restart
|
||||||
|
if (!berry.web_add_handler_done) {
|
||||||
|
bool network_up = WifiHasIP();
|
||||||
|
#ifdef USE_ETHERNET
|
||||||
|
network_up = network_up || EthernetHasIP();
|
||||||
|
#endif
|
||||||
|
if (network_up) { // if network is already up, send a synthetic event to trigger web handlers
|
||||||
|
callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr);
|
||||||
|
berry.web_add_handler_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (TasmotaGlobal.berry_fast_loop_enabled) { // call only if enabled at global level
|
if (TasmotaGlobal.berry_fast_loop_enabled) { // call only if enabled at global level
|
||||||
callBerryFastLoop(); // call `tasmota.fast_loop()` optimized for minimal performance impact
|
callBerryFastLoop(); // call `tasmota.fast_loop()` optimized for minimal performance impact
|
||||||
|
@ -822,7 +839,10 @@ bool Xdrv52(uint32_t function)
|
||||||
callBerryEventDispatcher(PSTR("web_add_config_button"), nullptr, 0, nullptr);
|
callBerryEventDispatcher(PSTR("web_add_config_button"), nullptr, 0, nullptr);
|
||||||
break;
|
break;
|
||||||
case FUNC_WEB_ADD_HANDLER:
|
case FUNC_WEB_ADD_HANDLER:
|
||||||
callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr);
|
if (!berry.web_add_handler_done) {
|
||||||
|
callBerryEventDispatcher(PSTR("web_add_handler"), nullptr, 0, nullptr);
|
||||||
|
berry.web_add_handler_done = true;
|
||||||
|
}
|
||||||
WebServer_on(PSTR("/bc"), HandleBerryConsole);
|
WebServer_on(PSTR("/bc"), HandleBerryConsole);
|
||||||
break;
|
break;
|
||||||
#endif // USE_WEBSERVER
|
#endif // USE_WEBSERVER
|
||||||
|
|
Loading…
Reference in New Issue