Skip to content

Commit

Permalink
BrRestart now supports web handlers to work after Berry restart (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
s-hadinger committed Jul 6, 2023
1 parent 862eddd commit 57c4825
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 0 additions & 1 deletion lib/libesp32/berry_mapping/src/be_cb_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
104 changes: 104 additions & 0 deletions lib/libesp32/berry_tasmota/src/be_webserver_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tasmota/tasmota_xdrv_driver/xdrv_52_0_berry_struct.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 38 additions & 18 deletions tasmota/tasmota_xdrv_driver/xdrv_52_3_berry_webserver.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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() : "<empty>", 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() : "<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");
}
}
// 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);
}
Expand Down
22 changes: 21 additions & 1 deletion tasmota/tasmota_xdrv_driver/xdrv_52_9_berry.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 57c4825

Please sign in to comment.