Make node.output() RTOS thread safe.
Also removed old, very unsafe node.osoutput(). We're now integrating cleanly with the IDF/newlib way of redirecting stdout. Added necessary depends in Kconfig to ensure VFS support is enabled, as otherwise you'd only get a mysterious crash when attempting to enable output redirection.
This commit is contained in:
parent
9a2fb84512
commit
a0c9085cca
|
@ -97,23 +97,3 @@ size_t feed_lua_input(const char *buf, size_t n)
|
|||
|
||||
return n; // we consumed/buffered all the provided data
|
||||
}
|
||||
|
||||
|
||||
void output_redirect(const char *str, size_t l) {
|
||||
lua_State *L = lua_getstate();
|
||||
int n = lua_gettop(L);
|
||||
lua_pushliteral(L, "stdout");
|
||||
lua_rawget(L, LUA_REGISTRYINDEX); /* fetch reg.stdout */
|
||||
if (lua_istable(L, -1)) { /* reg.stdout is pipe */
|
||||
lua_rawgeti(L, -1, 1); /* get the pipe_write func from stdout[1] */
|
||||
lua_insert(L, -2); /* and move above the pipe ref */
|
||||
lua_pushlstring(L, str, l);
|
||||
if (lua_pcall(L, 2, 0, 0) != LUA_OK) { /* Reg.stdout:write(str) */
|
||||
lua_writestringerror("error calling stdout:write(%s)\n", lua_tostring(L, -1));
|
||||
esp_restart();
|
||||
}
|
||||
} else { /* reg.stdout == nil */
|
||||
printf(str, l);
|
||||
}
|
||||
lua_settop(L, n); /* Make sure all code paths leave stack unchanged */
|
||||
}
|
||||
|
|
|
@ -305,7 +305,7 @@ extern int readline4lua(const char *prompt, char *buffer, int length);
|
|||
** They are only used in libraries and the stand-alone program. (The #if
|
||||
** avoids including 'stdio.h' everywhere.)
|
||||
*/
|
||||
#ifdef LUA_USE_ESP
|
||||
#ifdef LUA_USE_ESP8266
|
||||
void output_redirect(const char *str, size_t l);
|
||||
#define lua_writestring(s,l) output_redirect((s),(l))
|
||||
#else
|
||||
|
|
|
@ -230,7 +230,7 @@ LUALIB_API void (luaL_openlib) (lua_State *L, const char *libname,
|
|||
|
||||
/* print a string */
|
||||
#if !defined(lua_writestring)
|
||||
#ifdef LUA_USE_ESP
|
||||
#ifdef LUA_USE_ESP8266
|
||||
void output_redirect(const char *str, size_t l);
|
||||
#define lua_writestring(s,l) output_redirect((s),(l))
|
||||
#else
|
||||
|
|
|
@ -65,6 +65,8 @@ menu "NodeMCU modules"
|
|||
config NODEMCU_CMODULE_FILE
|
||||
bool "File module"
|
||||
default "y"
|
||||
select VFS_SUPPORT_IO
|
||||
select VFS_SUPPORT_DIR
|
||||
help
|
||||
Includes the file module (recommended).
|
||||
|
||||
|
@ -117,6 +119,7 @@ menu "NodeMCU modules"
|
|||
config NODEMCU_CMODULE_NET
|
||||
bool "Net module"
|
||||
default "y"
|
||||
select VFS_SUPPORT_IO
|
||||
help
|
||||
Includes the net module (recommended).
|
||||
|
||||
|
|
|
@ -399,26 +399,90 @@ static int node_input( lua_State* L )
|
|||
// When there is any write to the replaced stdout, our function redir_write will be called.
|
||||
// we can then invoke the lua callback.
|
||||
|
||||
static FILE *oldstdout; // keep the old stdout, e.g., the uart0
|
||||
lua_ref_t output_redir = LUA_NOREF; // this will hold the Lua callback
|
||||
int serial_debug = 0; // whether or not to write also to uart
|
||||
const char *VFS_REDIR = "/redir"; // virtual filesystem mount point
|
||||
// A buffer size that should be sufficient for most cases, yet not so large
|
||||
// as to present an issue.
|
||||
# define OUTPUT_CHUNK_SIZE 127
|
||||
typedef struct {
|
||||
uint8_t used;
|
||||
uint8_t bytes[OUTPUT_CHUNK_SIZE];
|
||||
} output_chunk_t;
|
||||
|
||||
static task_handle_t output_task; // for getting output into the LVM thread
|
||||
static lua_ref_t output_redir = LUA_NOREF; // this will hold the Lua callback
|
||||
static FILE *serial_debug; // the console uart, if wanted
|
||||
static const char *VFS_REDIR = "/redir"; // virtual filesystem mount point
|
||||
|
||||
// redir_write will be called everytime any code writes to stdout when
|
||||
// redirection is active
|
||||
// redirection is active, from ANY RTOS thread
|
||||
ssize_t redir_write(int fd, const void *data, size_t size) {
|
||||
if (serial_debug) // if serial_debug is nonzero, write to uart
|
||||
fwrite(data, sizeof(char), size, oldstdout);
|
||||
|
||||
if (output_redir != LUA_NOREF) { // prepare lua call
|
||||
lua_State *L = lua_getstate();
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, output_redir); // push function reference
|
||||
lua_pushlstring(L, (char *)data, size); // push data
|
||||
luaL_pcallx(L, 1, 0); // invoke callback
|
||||
UNUSED(fd);
|
||||
if (size)
|
||||
{
|
||||
size_t n = (size > OUTPUT_CHUNK_SIZE) ? OUTPUT_CHUNK_SIZE : size;
|
||||
output_chunk_t *chunk = malloc(sizeof(output_chunk_t));
|
||||
chunk->used = (uint8_t)n;
|
||||
memcpy(chunk->bytes, data, n);
|
||||
_Static_assert(sizeof(task_param_t) >= sizeof(chunk), "cast error below");
|
||||
if (!task_post_high(output_task, (task_param_t)chunk))
|
||||
{
|
||||
static const char overflow[] = "E: output overflow\n";
|
||||
fwrite(overflow, sizeof(overflow) -1, sizeof(char), serial_debug);
|
||||
free(chunk);
|
||||
return -1;
|
||||
}
|
||||
return size;
|
||||
|
||||
if (serial_debug)
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < n)
|
||||
{
|
||||
size_t w = fwrite(
|
||||
data + written, sizeof(char), n - written, serial_debug);
|
||||
if (w > 0)
|
||||
written += w;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
void redir_output(task_param_t param, task_prio_t prio)
|
||||
{
|
||||
UNUSED(prio);
|
||||
output_chunk_t *chunk = (output_chunk_t *)param;
|
||||
bool redir_active = (output_redir != LUA_NOREF);
|
||||
if (redir_active)
|
||||
{
|
||||
lua_State *L = lua_getstate();
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, output_redir);
|
||||
lua_pushlstring(L, (char *)chunk->bytes, chunk->used);
|
||||
luaL_pcallx(L, 1, 0);
|
||||
}
|
||||
free(chunk);
|
||||
}
|
||||
|
||||
#if !defined(CONFIG_ESP_CONSOLE_NONE)
|
||||
static const char *default_console_name(void)
|
||||
{
|
||||
return
|
||||
#if defined(CONFIG_ESP_CONSOLE_UART)
|
||||
# define STRINGIFY(x) STRINGIFY2(x)
|
||||
# define STRINGIFY2(x) #x
|
||||
"/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
#undef STRINGIFY2
|
||||
#undef STRINGIFY
|
||||
#elif defined(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||
"/dev/cdcacm";
|
||||
#elif defined(CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG)
|
||||
"/dev/usbserjtag";
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// redir_open is called when fopen() is called on /redir/xxx
|
||||
int redir_open(const char *path, int flags, int mode) {
|
||||
return 79; // since we only have one "file", just return some fd number to make the VFS system happy
|
||||
|
@ -426,6 +490,11 @@ int redir_open(const char *path, int flags, int mode) {
|
|||
|
||||
// Lua: node.output(func, serial_debug)
|
||||
static int node_output(lua_State *L) {
|
||||
if (serial_debug)
|
||||
{
|
||||
fclose(serial_debug);
|
||||
serial_debug = NULL;
|
||||
}
|
||||
if (lua_isfunction(L, 1)) {
|
||||
if (output_redir == LUA_NOREF) {
|
||||
// create an instance of a virtual filesystem so we can use fopen
|
||||
|
@ -439,69 +508,25 @@ static int node_output(lua_State *L) {
|
|||
};
|
||||
// register this filesystem under the `/redir` namespace
|
||||
ESP_ERROR_CHECK(esp_vfs_register(VFS_REDIR, &redir_fs, NULL));
|
||||
oldstdout = stdout; // save the previous stdout
|
||||
stdout = fopen(VFS_REDIR, "w"); // open the new one for writing
|
||||
freopen(VFS_REDIR, "w", stdout);
|
||||
|
||||
if (lua_isnoneornil(L, 2) ||
|
||||
(lua_isnumber(L, 2) && lua_tonumber(L, 2)))
|
||||
serial_debug = fopen(default_console_name(), "w");
|
||||
} else {
|
||||
luaX_unset_ref(L, &output_redir); // dereference previous callback
|
||||
}
|
||||
luaX_set_ref(L, 1, &output_redir); // set the callback
|
||||
} else {
|
||||
if (output_redir != LUA_NOREF) {
|
||||
fclose(stdout); // close the redirected stdout
|
||||
stdout = oldstdout; // restore original stdout
|
||||
ESP_ERROR_CHECK(esp_vfs_unregister(VFS_REDIR)); // unregister redir filesystem
|
||||
luaX_unset_ref(L, &output_redir); // forget callback
|
||||
}
|
||||
serial_debug = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// second parameter indicates whether output will also be sent to old stdout
|
||||
if (lua_isnumber(L, 2)) {
|
||||
serial_debug = lua_tointeger(L, 2);
|
||||
if (serial_debug != 0)
|
||||
serial_debug = 1;
|
||||
} else {
|
||||
serial_debug = 1; // default to 1
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The implementation of node.osoutput redirect all OS logging to Lua space
|
||||
lua_ref_t os_output_redir = LUA_NOREF; // this will hold the Lua callback
|
||||
static vprintf_like_t oldvprintf; // keep the old vprintf
|
||||
|
||||
// redir_vprintf will be called everytime the OS attempts to print a trace statement
|
||||
int redir_vprintf(const char *fmt, va_list ap)
|
||||
{
|
||||
static char data[128];
|
||||
int size = vsnprintf(data, 128, fmt, ap);
|
||||
|
||||
if (os_output_redir != LUA_NOREF) { // prepare lua call
|
||||
lua_State *L = lua_getstate();
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, os_output_redir); // push function reference
|
||||
lua_pushlstring(L, (char *)data, size); // push data
|
||||
luaL_pcallx(L, 1, 0); // invoke callback
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
// Lua: node.output(func, serial_debug)
|
||||
static int node_osoutput(lua_State *L) {
|
||||
if (lua_isfunction(L, 1)) {
|
||||
if (os_output_redir == LUA_NOREF) {
|
||||
// register our log redirect first time this is invoked
|
||||
oldvprintf = esp_log_set_vprintf(redir_vprintf);
|
||||
} else {
|
||||
luaX_unset_ref(L, &os_output_redir); // dereference previous callback
|
||||
}
|
||||
luaX_set_ref(L, 1, &os_output_redir); // set the callback
|
||||
} else {
|
||||
if (os_output_redir != LUA_NOREF) {
|
||||
esp_log_set_vprintf(oldvprintf);
|
||||
luaX_unset_ref(L, &os_output_redir); // forget callback
|
||||
#if defined(CONFIG_ESP_CONSOLE_NONE)
|
||||
fclose(stdout);
|
||||
#else
|
||||
// reopen the console device onto the stdout stream
|
||||
freopen(default_console_name(), "w", stdout);
|
||||
#endif
|
||||
ESP_ERROR_CHECK(esp_vfs_unregister(VFS_REDIR));
|
||||
luaX_unset_ref(L, &output_redir);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -836,7 +861,6 @@ LROT_BEGIN(node, NULL, 0)
|
|||
LROT_FUNCENTRY( heap, node_heap )
|
||||
LROT_FUNCENTRY( input, node_input )
|
||||
LROT_FUNCENTRY( output, node_output )
|
||||
LROT_FUNCENTRY( osoutput, node_osoutput )
|
||||
LROT_FUNCENTRY( osprint, node_osprint )
|
||||
LROT_FUNCENTRY( restart, node_restart )
|
||||
LROT_FUNCENTRY( setonerror, node_setonerror )
|
||||
|
@ -849,6 +873,8 @@ LROT_END(node, NULL, 0)
|
|||
|
||||
int luaopen_node(lua_State *L)
|
||||
{
|
||||
output_task = task_get_id(redir_output);
|
||||
|
||||
lua_settop(L, 0);
|
||||
return node_setonerror(L); /* set default onerror action */
|
||||
}
|
||||
|
|
|
@ -301,68 +301,20 @@ Reload LFS with the flash image provided. Flash images can be generated on the h
|
|||
- The reload process internally makes multiple passes through the LFS image file. The first pass validates the file and header formats and detects many errors. If any is detected then an error string is returned.
|
||||
|
||||
|
||||
## node.key() --deprecated
|
||||
|
||||
Defines action to take on button press (on the old devkit 0.9), button connected to GPIO 16.
|
||||
|
||||
This function is only available if the firmware was compiled with DEVKIT_VERSION_0_9 defined.
|
||||
|
||||
#### Syntax
|
||||
`node.key(type, function())`
|
||||
|
||||
#### Parameters
|
||||
- `type`: type is either string "long" or "short". long: press the key for 3 seconds, short: press shortly(less than 3 seconds)
|
||||
- `function`: user defined function which is called when key is pressed. If nil, remove the user defined function. Default function: long: change LED blinking rate, short: reset chip
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
node.key("long", function() print('hello world') end)
|
||||
```
|
||||
#### See also
|
||||
[`node.led()`](#nodeled-deprecated)
|
||||
|
||||
## node.led() --deprecated
|
||||
|
||||
Sets the on/off time for the LED (on the old devkit 0.9), with the LED connected to GPIO16, multiplexed with [`node.key()`](#nodekey-deprecated).
|
||||
|
||||
This function is only available if the firmware was compiled with DEVKIT_VERSION_0_9 defined.
|
||||
|
||||
#### Syntax
|
||||
`node.led(low, high)`
|
||||
|
||||
#### Parameters
|
||||
- `low` LED off time, LED keeps on when low=0. Unit: milliseconds, time resolution: 80~100ms
|
||||
- `high` LED on time. Unit: milliseconds, time resolution: 80~100ms
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
-- turn led on forever.
|
||||
node.led(0)
|
||||
```
|
||||
|
||||
#### See also
|
||||
[`node.key()`](#nodekey-deprecated)
|
||||
|
||||
## node.output()
|
||||
|
||||
Redirects the Lua interpreter output to a callback function. Optionally also prints it to the serial console.
|
||||
Redirects all standard output (`stdout`) to a callback function. Optionally also prints it to the console device, as specified in Kconfig under the "ESP System Settings" section.
|
||||
|
||||
!!! note "Note:"
|
||||
|
||||
Do **not** attempt to `print()` or otherwise induce the Lua interpreter to produce output from within the callback function. Doing so results in infinite recursion, and leads to a watchdog-triggered restart.
|
||||
Do **not** attempt to `print()` or otherwise induce the Lua interpreter to produce output from within the callback function. Doing so results in infinite recursion, and leads to a crash or watchdog-triggered restart.
|
||||
|
||||
#### Syntax
|
||||
`node.output(function(str), serial_output)`
|
||||
`node.output(function(str), console_output)`
|
||||
|
||||
#### Parameters
|
||||
- `output_fn(str)` a function accept every output as str, and can send the output to a socket (or maybe a file). `nil` to unregister the previous function.
|
||||
- `serial_output` 1 output also sent out the serial port. 0: no serial output. Defaults to 1 if not specified.
|
||||
- `console_output` 1 output also sent out the console port. 0: no console output. Defaults to 1 if not specified.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
@ -372,7 +324,7 @@ Redirects the Lua interpreter output to a callback function. Optionally also pri
|
|||
function tonet(str)
|
||||
sk:send(str)
|
||||
end
|
||||
node.output(tonet, 1) -- serial also get the lua output.
|
||||
node.output(tonet, 1) -- console also get the lua output.
|
||||
```
|
||||
|
||||
```lua
|
||||
|
@ -403,37 +355,6 @@ node.output(nil, 0)
|
|||
#### See also
|
||||
[`node.input()`](#nodeinput)
|
||||
|
||||
## node.osoutput()
|
||||
|
||||
Redirects the debugging output from the Espressif SDK to a callback function allowing it to be captured or processed in Lua.
|
||||
|
||||
####Syntax
|
||||
`node.osoutput(function(str))`
|
||||
|
||||
#### Parameters
|
||||
|
||||
- `function(str)` a function accepts debugging output as str, and can send the output to a socket (or maybe a file). `nil` to unregister the previous function.
|
||||
|
||||
#### Returns
|
||||
|
||||
Nothing
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
function luaprint(str)
|
||||
print("lua space: "str)
|
||||
end
|
||||
node.osoutput(luaprint)
|
||||
```
|
||||
|
||||
```lua
|
||||
-- disable all output completely
|
||||
node.osoutput(nil)
|
||||
```
|
||||
|
||||
## node.readvdd33() --deprecated
|
||||
Moved to [`adc.readvdd33()`](adc/#adcreadvdd33).
|
||||
|
||||
## node.restart()
|
||||
|
||||
|
|
Loading…
Reference in New Issue