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
|
||||
- Berry various fixes for Walrus Operator (#18982)
|
||||
- MiElHVAC power commands regression from v12.4.0.1 (#18923)
|
||||
- `BrRestart` now supports web handlers to work after Berry restart
|
||||
|
||||
### Removed
|
||||
- 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;
|
||||
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)
|
||||
bvalue *top = be_incrtop(vm);
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
#ifdef USE_WEBSERVER
|
||||
|
||||
#include "be_exec.h"
|
||||
#include "be_vm.h"
|
||||
|
||||
extern int w_webserver_member(bvm *vm);
|
||||
extern int w_webserver_on(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);
|
||||
|
||||
|
||||
// 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
|
||||
module webserver (scope: global) {
|
||||
member, func(w_webserver_member)
|
||||
|
|
|
@ -92,6 +92,7 @@ public:
|
|||
bvm *vm = nullptr; // berry vm
|
||||
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 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 repl_active = false; // is REPL running (activates log recording)
|
||||
// output log is stored as a LinkedList of buffers
|
||||
|
|
|
@ -70,7 +70,14 @@ extern "C" {
|
|||
* 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" {
|
||||
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`
|
||||
//
|
||||
// WARNING - this should be called only when receiving `web_add_handler` event.
|
||||
|
@ -88,27 +95,40 @@ extern "C" {
|
|||
method = be_toint(vm, 3);
|
||||
}
|
||||
|
||||
be_getglobal(vm, PSTR("tasmota"));
|
||||
if (!be_isnil(vm, -1)) {
|
||||
be_getmethod(vm, -1, PSTR("gen_cb"));
|
||||
if (!be_isnil(vm, -1)) {
|
||||
be_pushvalue(vm, -2); // add instance as first arg
|
||||
be_pushvalue(vm, 2); // push closure as second arg
|
||||
be_pcall(vm, 2); // 2 arguments
|
||||
be_pop(vm, 2);
|
||||
// find if the prefix/method is already defined
|
||||
int32_t slot;
|
||||
for (slot = 0; slot < WEBSERVER_REQ_HANDLER_HOOK_MAX; slot++) {
|
||||
// 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]);
|
||||
if (be_webserver_prefix[slot] == prefix && be_webserver_method[slot] == method) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (be_iscomptr(vm, -1)) { // sanity check
|
||||
const void * cb = be_tocomptr(vm, -1);
|
||||
// All good, we can proceed
|
||||
|
||||
WebServer_on(prefix, (void (*)()) cb, method);
|
||||
be_return_nil(vm); // return, all good
|
||||
if (slot >= WEBSERVER_REQ_HANDLER_HOOK_MAX) {
|
||||
// we didn't find a duplicate, let's find a free slot
|
||||
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]);
|
||||
if (be_webserver_prefix[slot].equals("")) {
|
||||
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
|
||||
be_raise(vm, kInternalError, nullptr);
|
||||
// AddLog(LOG_LEVEL_INFO, ">>>: slot found = %i", slot);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -304,12 +304,17 @@ void BrShowState(void) {
|
|||
/*********************************************************************************************\
|
||||
* VM Init
|
||||
\*********************************************************************************************/
|
||||
extern "C" void be_webserver_cb_deinit(bvm *vm);
|
||||
void BerryInit(void) {
|
||||
// clean previous VM if any
|
||||
if (berry.vm != nullptr) {
|
||||
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);
|
||||
berry.vm = nullptr;
|
||||
berry.web_add_handler_done = false;
|
||||
berry.autoexec_done = false;
|
||||
berry.repl_active = 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
|
||||
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
|
||||
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);
|
||||
break;
|
||||
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);
|
||||
break;
|
||||
#endif // USE_WEBSERVER
|
||||
|
|
Loading…
Reference in New Issue