diff --git a/CHANGELOG.md b/CHANGELOG.md index a818d027d77b..8f3fc8ea64f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/libesp32/berry_mapping/src/be_cb_module.c b/lib/libesp32/berry_mapping/src/be_cb_module.c index b5e7b1427605..7bf729a0b018 100644 --- a/lib/libesp32/berry_mapping/src/be_cb_module.c +++ b/lib/libesp32/berry_mapping/src/be_cb_module.c @@ -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); diff --git a/lib/libesp32/berry_tasmota/src/be_webserver_lib.c b/lib/libesp32/berry_tasmota/src/be_webserver_lib.c index 0cdf4ede0e5e..541233893467 100644 --- a/lib/libesp32/berry_tasmota/src/be_webserver_lib.c +++ b/lib/libesp32/berry_tasmota/src/be_webserver_lib.c @@ -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) diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_0_berry_struct.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_0_berry_struct.ino index ac152d9aaff4..ba32bd06f5a3 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_0_berry_struct.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_0_berry_struct.ino @@ -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 diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_webserver.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_webserver.ino index 7df813e3f247..244e594cf7a7 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_webserver.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_webserver.ino @@ -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); - - 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 + // 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() : "", be_webserver_method[slot]); + if (be_webserver_prefix[slot] == prefix && be_webserver_method[slot] == method) { + break; + } + } + + 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() : "", 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"); + } + } + // 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 } - // be_pop(vm, 1); // not really useful since we raise an exception anyways - be_raise(vm, kInternalError, nullptr); + 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); } diff --git a/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino b/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino index f39a45271536..49255b436619 100644 --- a/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino +++ b/tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino @@ -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