From 13553ddf7644fd26e7119211b80e8173ad315b83 Mon Sep 17 00:00:00 2001 From: Jade Mattsson Date: Tue, 22 Oct 2024 10:23:17 +1100 Subject: [PATCH] Refactor into new 'console' module. A breaking change, but should finally see us move away from the chronic edge cases and inconsistent behaviour we have while trying to shoe-horn the usb-serial-jtag and cdc-acm consoles into uart behaviour and assumptions. --- components/base_nodemcu/user_main.c | 154 +---------------- components/lua/Kconfig | 2 +- components/modules/CMakeLists.txt | 2 + components/modules/Kconfig | 8 +- components/modules/console.c | 256 ++++++++++++++++++++++++++++ components/modules/serial_common.c | 197 +++++++++++++++++++++ components/modules/serial_common.h | 91 ++++++++++ components/modules/uart.c | 248 +++++++-------------------- components/platform/platform.c | 50 +++--- docs/modules/console.md | 88 ++++++++++ docs/modules/uart.md | 45 +++-- mkdocs.yml | 1 + 12 files changed, 753 insertions(+), 389 deletions(-) create mode 100644 components/modules/console.c create mode 100644 components/modules/serial_common.c create mode 100644 components/modules/serial_common.h create mode 100644 docs/modules/console.md diff --git a/components/base_nodemcu/user_main.c b/components/base_nodemcu/user_main.c index 0a70e58b..64bdea5b 100644 --- a/components/base_nodemcu/user_main.c +++ b/components/base_nodemcu/user_main.c @@ -11,47 +11,18 @@ #include "lua.h" #include "linput.h" #include "platform.h" -#include -#include -#include #include "sdkconfig.h" #include "esp_system.h" #include "esp_event.h" #include "esp_spiffs.h" #include "esp_netif.h" -#include "esp_vfs_dev.h" -#include "esp_vfs_cdcacm.h" -#include "esp_vfs_usb_serial_jtag.h" -#include "driver/usb_serial_jtag.h" #include "nvs_flash.h" #include "task/task.h" -#include "sections.h" #include "nodemcu_esp_event.h" #include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" - -#define SIG_LUA 0 -#define SIG_UARTINPUT 1 - -// Line ending config from Kconfig -#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF -# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CRLF -#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR -# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CR -#else -# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_LF -#endif - -#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF -# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CRLF -#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR -# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CR -#else -# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_LF -#endif +#include "freertos/semphr.h" // We don't get argument size data from the esp_event dispatch, so it's @@ -71,8 +42,6 @@ typedef struct { static task_handle_t relayed_event_task; static SemaphoreHandle_t relayed_event_handled; -static task_handle_t lua_feed_task; - // This function runs in the context of the system default event loop RTOS task static void relay_default_loop_events( @@ -166,129 +135,10 @@ static void nodemcu_init(void) } -static bool have_console_on_data_cb(void) -{ -#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM - return uart_has_on_data_cb(CONFIG_ESP_CONSOLE_UART_NUM); -#else - return false; -#endif -} - - -static void console_nodemcu_task(task_param_t param, task_prio_t prio) -{ - (void)prio; - char c = (char)param; - - if (run_input) - feed_lua_input(&c, 1); - -#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM - if (have_console_on_data_cb()) - uart_feed_data(CONFIG_ESP_CONSOLE_UART_NUM, &c, 1); -#endif - - // The IDF doesn't seem to honor setvbuf(stdout, NULL, _IONBF, 0) :( - fsync(fileno(stdout)); -} - - -static void console_task(void *) -{ - for (;;) - { - /* We can't use a large read buffer here as some console choices - * (e.g. usb-serial-jtag) don't support read timeouts/partial reads, - * which breaks the echo support and makes for a bad user experience. - */ - char c; - ssize_t n = read(fileno(stdin), &c, 1); - if (n > 0 && (run_input || have_console_on_data_cb())) - { - if (!task_post_block_high(lua_feed_task, (task_param_t)c)) - { - NODE_ERR("Lost console input data?!\n"); - } - } - } -} - - -static void console_init(void) -{ - fflush(stdout); - fsync(fileno(stdout)); - - /* Disable buffering */ - setvbuf(stdin, NULL, _IONBF, 0); - setvbuf(stdout, NULL, _IONBF, 0); - - /* Disable non-blocking mode */ - fcntl(fileno(stdin), F_SETFL, 0); - fcntl(fileno(stdout), F_SETFL, 0); - -#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM - /* Based on console/advanced example */ - - esp_vfs_dev_uart_port_set_rx_line_endings( - CONFIG_ESP_CONSOLE_UART_NUM, RX_LINE_ENDINGS_CFG); - esp_vfs_dev_uart_port_set_tx_line_endings( - CONFIG_ESP_CONSOLE_UART_NUM, TX_LINE_ENDINGS_CFG); - - /* Configure UART. Note that REF_TICK is used so that the baud rate remains - * correct while APB frequency is changing in light sleep mode. - */ - const uart_config_t uart_config = { - .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, -#if SOC_UART_SUPPORT_REF_TICK - .source_clk = UART_SCLK_REF_TICK, -#elif SOC_UART_SUPPORT_XTAL_CLK - .source_clk = UART_SCLK_XTAL, -#endif - }; - /* Install UART driver for interrupt-driven reads and writes */ - uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); - uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config); - - /* Tell VFS to use UART driver */ - esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); - -#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - /* Based on @pjsg's work */ - - esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(RX_LINE_ENDINGS_CFG); - esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(TX_LINE_ENDINGS_CFG); - - usb_serial_jtag_driver_config_t usb_serial_jtag_config = - USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); - /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and write */ - usb_serial_jtag_driver_install(&usb_serial_jtag_config); - - esp_vfs_usb_serial_jtag_use_driver(); -#elif CONFIG_ESP_CONSOLE_USB_CDC - /* Based on console/advanced_usb_cdc */ - - esp_vfs_dev_cdcacm_set_rx_line_endings(RX_LINE_ENDINGS_CFG); - esp_vfs_dev_cdcacm_set_tx_line_endings(TX_LINE_ENDINGS_CFG); -#else -# error "Unsupported console type" -#endif - - xTaskCreate( - console_task, "console", 1024, NULL, ESP_TASK_MAIN_PRIO+1, NULL); -} - - void __attribute__((noreturn)) app_main(void) { task_init(); - lua_feed_task = task_get_id(console_nodemcu_task); - relayed_event_handled = xSemaphoreCreateBinary(); relayed_event_task = task_get_id(handle_default_loop_event); @@ -304,8 +154,6 @@ void __attribute__((noreturn)) app_main(void) nvs_flash_init (); esp_netif_init (); - console_init(); - start_lua (); task_pump_messages (); __builtin_unreachable (); diff --git a/components/lua/Kconfig b/components/lua/Kconfig index 695101a5..ae186bde 100644 --- a/components/lua/Kconfig +++ b/components/lua/Kconfig @@ -129,7 +129,7 @@ menu "Lua configuration" bool default y select NODEMCU_CMODULE_PIPE - select NODEMCU_CMODULE_UART + select NODEMCU_CMODULE_CONSOLE select LUA_BUILTIN_DEBUG choice LUA_INIT_STRING diff --git a/components/modules/CMakeLists.txt b/components/modules/CMakeLists.txt index 6d60e04d..0fab0691 100644 --- a/components/modules/CMakeLists.txt +++ b/components/modules/CMakeLists.txt @@ -11,6 +11,7 @@ set(module_srcs "bit.c" "bthci.c" "common.c" + "console.c" "crypto.c" "dht.c" "encoder.c" @@ -35,6 +36,7 @@ set(module_srcs "rmt.c" "rtcmem.c" "qrcodegen.c" + "serial_common.c" "sigma_delta.c" "sjson.c" "sodium.c" diff --git a/components/modules/Kconfig b/components/modules/Kconfig index ce13f848..c393ab6b 100644 --- a/components/modules/Kconfig +++ b/components/modules/Kconfig @@ -28,6 +28,12 @@ menu "NodeMCU modules" help Includes the can module. + config NODEMCU_CMODULE_CONSOLE + bool "Console module" + default y + help + Includes the console module (required by our Lua VM). + config NODEMCU_CMODULE_CRYPTO bool "Crypto module" default "n" @@ -339,6 +345,6 @@ menu "NodeMCU modules" bool "UART module" default y help - Includes the UART module (required by our Lua VM). + Includes the UART module. endmenu diff --git a/components/modules/console.c b/components/modules/console.c new file mode 100644 index 00000000..cd200db9 --- /dev/null +++ b/components/modules/console.c @@ -0,0 +1,256 @@ +#include "module.h" +#include "platform.h" +#include "lauxlib.h" +#include "linput.h" +#include "serial_common.h" +#include "task/task.h" + +#include "esp_vfs_dev.h" +#include "esp_vfs_cdcacm.h" +#include "esp_vfs_usb_serial_jtag.h" +#include "driver/usb_serial_jtag.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include +#include +#include + +// Line ending config from Kconfig +#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF +# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR +# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CR +#else +# define RX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_LF +#endif + +#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF +# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CRLF +#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR +# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_CR +#else +# define TX_LINE_ENDINGS_CFG ESP_LINE_ENDINGS_LF +#endif + +typedef enum { NONINTERACTIVE, INTERACTIVE } console_mode_t; + +static serial_input_cfg_t *cb_cfg; +static task_handle_t feed_lua_task; + + +// --- Console input task related ----------------------------------- + +static void console_feed_lua(task_param_t param, task_prio_t prio) +{ + (void)prio; + char c = (char)param; + + if (run_input) + feed_lua_input(&c, 1); + + if (serial_input_has_data_cb(cb_cfg)) + serial_input_feed_data(cb_cfg, &c, 1); + + // The IDF doesn't seem to honor setvbuf(stdout, NULL, _IONBF, 0) :( + fflush(stdout); + fsync(fileno(stdout)); +} + + +static void console_task(void *) +{ + for (;;) + { + // TODO: Support linenoise editing here as an option? + // The run_input switch would need to also control whether we do + // linenoise or raw byte input, to allow for binary xfers. + // But, we would have a big race condition here as the execution + // of the last line happens after we've already started reading the + // next one. We'd have to use a newer version of linenoise than what + // the IDF has, so we get the async interface. Plus switch everything to + // using select() before picking which input method we're using. + // For the race condition, would it be sufficient to wait for the next + // next prompt display to be reasonably certain it's switched? + // But even the prompt handling would be problematic with linenoise as + // that's fixed on linenoiseEditStart(). To solve that we'd need to + // be running the console within the LVM task, synchronously, but we + // can't do that because we need the LVM accessible to handle events. + // These are incompatible design constraints, sigh. + + /* We can't use a large read buffer here as some console choices + * (e.g. usb-serial-jtag) don't support read timeouts/partial reads, + * which breaks the echo support and makes for a bad user experience. + */ + char c; + ssize_t n = read(fileno(stdin), &c, 1); + if (n > 0 && (run_input || serial_input_has_data_cb(cb_cfg))) + { + if (!task_post_block_high(feed_lua_task, (task_param_t)c)) + { + NODE_ERR("Lost console input data?!\n"); + } + } + } +} + + +static void console_init(void) +{ + fflush(stdout); + fsync(fileno(stdout)); + + /* Disable buffering */ + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + /* Disable non-blocking mode */ + fcntl(fileno(stdin), F_SETFL, 0); + fcntl(fileno(stdout), F_SETFL, 0); + +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + /* Based on console/advanced example */ + + esp_vfs_dev_uart_port_set_rx_line_endings( + CONFIG_ESP_CONSOLE_UART_NUM, RX_LINE_ENDINGS_CFG); + esp_vfs_dev_uart_port_set_tx_line_endings( + CONFIG_ESP_CONSOLE_UART_NUM, TX_LINE_ENDINGS_CFG); + + /* Configure UART. Note that REF_TICK is used so that the baud rate remains + * correct while APB frequency is changing in light sleep mode. + */ + const uart_config_t uart_config = { + .baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, +#if SOC_UART_SUPPORT_REF_TICK + .source_clk = UART_SCLK_REF_TICK, +#elif SOC_UART_SUPPORT_XTAL_CLK + .source_clk = UART_SCLK_XTAL, +#endif + }; + /* Install UART driver for interrupt-driven reads and writes */ + uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, 256, 0, 0, NULL, 0); + uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); + +#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG + /* Based on @pjsg's work */ + + esp_vfs_dev_usb_serial_jtag_set_rx_line_endings(RX_LINE_ENDINGS_CFG); + esp_vfs_dev_usb_serial_jtag_set_tx_line_endings(TX_LINE_ENDINGS_CFG); + + usb_serial_jtag_driver_config_t usb_serial_jtag_config = + USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(); + /* Install USB-SERIAL-JTAG driver for interrupt-driven reads and write */ + usb_serial_jtag_driver_install(&usb_serial_jtag_config); + + esp_vfs_usb_serial_jtag_use_driver(); +#elif CONFIG_ESP_CONSOLE_USB_CDC + /* Based on console/advanced_usb_cdc */ + + esp_vfs_dev_cdcacm_set_rx_line_endings(RX_LINE_ENDINGS_CFG); + esp_vfs_dev_cdcacm_set_tx_line_endings(TX_LINE_ENDINGS_CFG); +#else +# error "Unsupported console type" +#endif + + xTaskCreate( + console_task, "console", configMINIMAL_STACK_SIZE, + NULL, ESP_TASK_MAIN_PRIO+1, NULL); +} + + +// --- Lua interface related ---------------------------------------- + +static int retrying_write(const char *buf, size_t len) +{ + size_t written = 0; + while (written < len) + { + size_t n = fwrite(buf + written, 1, len - written, stdout); + if (n > 0) + written += n; + else if (ferror(stdout)) + break; + else + vTaskDelay(1); + } + return written; +} + + +// Lua: console.on("method", [number/char], function) +static int console_on(lua_State *L) +{ + return serial_input_register(L, cb_cfg); +} + + +// Lua: console.mode(onoff) +static int console_mode(lua_State *L) +{ + switch (luaL_checkint(L, 1)) + { + case NONINTERACTIVE: run_input = false; break; + case INTERACTIVE: run_input = true; break; + default: luaL_error(L, "invalid mode"); + } + return 0; +} + + +// Lua: console.write(string1, [string2], ..., [stringn]) +static int console_write(lua_State *L) +{ + int total = lua_gettop(L); + for (int s = 1; s <= total; ++s) + { + if (lua_isnumber(L, s)) + { + int n = lua_tointeger(L, s); + if (n < 0 || n > 255) + return luaL_error(L, "invalid number"); + char ch = n; + retrying_write(&ch, 1); + } + else + { + luaL_checktype(L, s, LUA_TSTRING); + size_t len = 0; + const char *buf = lua_tolstring(L, s, &len); + retrying_write(buf, len); + } + } + fflush(stdout); + fsync(fileno(stdout)); + return 0; +} + + +// Module function map +LROT_BEGIN(console, NULL, 0) + LROT_FUNCENTRY( mode, console_mode ) + LROT_FUNCENTRY( on, console_on ) + LROT_FUNCENTRY( write, console_write ) + LROT_NUMENTRY( INTERACTIVE, INTERACTIVE ) + LROT_NUMENTRY( NONINTERACTIVE, NONINTERACTIVE ) +LROT_END(console, NULL, 0) + + +int luaopen_console( lua_State *L ) { + cb_cfg = serial_input_new(); + if (!cb_cfg) + return luaL_error(L, "out of mem"); + + feed_lua_task = task_get_id(console_feed_lua); + + console_init(); + + return 0; +} + +NODEMCU_MODULE(UART, "console", console, luaopen_console); diff --git a/components/modules/serial_common.c b/components/modules/serial_common.c new file mode 100644 index 00000000..c0f6b426 --- /dev/null +++ b/components/modules/serial_common.c @@ -0,0 +1,197 @@ +// Common routines for handling serial input data + +#include "serial_common.h" +#include "lauxlib.h" +#include + +// This is the historical max value +#define MAX_SERIAL_INPUT 255 + +struct serial_input_cfg { + int receive_ref; + int error_ref; + char *line_buffer; + size_t line_buffer_size; + size_t line_position; + uint16_t need_len; + int16_t end_char; +}; + + +static const char nostack[] = "out of stack"; + +static bool serial_input_invoke(int ref, const char *buf, size_t len) +{ + if(ref == LUA_NOREF || !buf || len == 0) + return false; + + lua_State *L = lua_getstate(); + + int top = lua_gettop(L); + luaL_checkstack(L, 2, nostack); + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_pushlstring(L, buf, len); + luaL_pcallx(L, 1, 0); + lua_settop(L, top); + return true; +} + + +serial_input_cfg_t *serial_input_new(void) +{ + serial_input_cfg_t *cfg = calloc(1, sizeof(serial_input_cfg_t)); + if (!cfg) + return NULL; + cfg->receive_ref = cfg->error_ref = LUA_NOREF; + cfg->end_char = -1; + return cfg; +} + + +void serial_input_free(lua_State *L, serial_input_cfg_t *cfg) +{ + if (cfg->receive_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, cfg->receive_ref); + if (cfg->error_ref != LUA_NOREF) + luaL_unref(L, LUA_REGISTRYINDEX, cfg->error_ref); + free(cfg->line_buffer); + free(cfg); +} + + +bool serial_input_dispatch_data(serial_input_cfg_t *cfg, const char *buf, size_t len) +{ + if (!cfg) + return false; + else + return serial_input_invoke(cfg->receive_ref, buf, len); +} + + +bool serial_input_report_error(serial_input_cfg_t *cfg, const char *buf, size_t len) +{ + if (!cfg) + return false; + else + return serial_input_invoke(cfg->error_ref, buf, len); +} + + +void serial_input_feed_data(serial_input_cfg_t *cfg, const char *buf, size_t len) +{ + if (!cfg || !cfg->line_buffer || !buf || !len) + return; + + const uint16_t need_len = cfg->need_len; + const int16_t end_char = cfg->end_char; + const size_t max_wanted = + (end_char >= 0 && need_len == 0) ? cfg->line_buffer_size : need_len; + + for (unsigned i = 0; i < len; ++i) + { + char ch = buf[i]; + cfg->line_buffer[cfg->line_position] = ch; + cfg->line_position++; + + bool at_end = (cfg->line_position >= max_wanted); + bool end_char_found = + (end_char >= 0 && (uint8_t)ch == (uint8_t)end_char); + if (at_end || end_char_found) { + // Reset line position early so callback can resize line_buffer if desired + int n = cfg->line_position; + cfg->line_position = 0; + serial_input_dispatch_data(cfg, cfg->line_buffer, n); + } + } +} + + +bool serial_input_has_data_cb(serial_input_cfg_t *cfg) +{ + return cfg && cfg->receive_ref != LUA_NOREF; +} + + +// on("method", [number/char], function) +int serial_input_register(lua_State *L, serial_input_cfg_t *cfg) +{ + const char *method = luaL_checkstring(L, 1); + const bool is_data = (strcmp(method, "data") == 0); + const bool is_error = (strcmp(method, "error") == 0); + if (!is_data && !is_error) + return luaL_error(L, "method not supported"); + + int fn_idx = -1; + + if (lua_isnumber(L, 2)) + { + cfg->need_len = luaL_checkinteger(L, 2); + cfg->end_char = -1; + } + else if (lua_isstring(L, 2)) + { + size_t len; + const char *end = luaL_checklstring(L, 2, &len); + if (len != 1) + return luaL_error(L, "only single byte end marker supported"); + cfg->need_len = 0; + cfg->end_char = end[0]; + } + else if (lua_isfunction(L, 2)) + { + fn_idx = 2; + } + + if (fn_idx == -1 && lua_isfunction(L, 3)) + { + fn_idx = 3; + } + + + if (is_data) + { + if (cfg->receive_ref != LUA_NOREF) + luaL_unref2(L, LUA_REGISTRYINDEX, cfg->receive_ref); // unref & clear + + if (fn_idx != -1) // Register and (re)alloc resources + { + luaL_checkstack(L, 1, nostack); + lua_pushvalue(L, fn_idx); + cfg->receive_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + size_t min_size = (cfg->need_len > 0) ? cfg->need_len : MAX_SERIAL_INPUT; + // Prevent dropping input; this should be an exceedingly rare condition + if (cfg->line_position >= min_size) + min_size = cfg->line_position + 1; + + if (cfg->line_buffer_size < min_size) + { + cfg->line_buffer = realloc(cfg->line_buffer, min_size); + cfg->line_buffer_size = (cfg->line_buffer) ? min_size : 0; + if (!cfg->line_buffer) + return luaL_error(L, "out of mem"); + } + } + else // Free resources + { + free(cfg->line_buffer); + cfg->line_buffer = NULL; + cfg->line_buffer_size = 0; + cfg->line_position = 0; + } + } + else if (is_error) + { + if (cfg->error_ref != LUA_NOREF) + luaL_unref2(L, LUA_REGISTRYINDEX, cfg->error_ref); // unref & clear + + if (fn_idx != -1) + { + luaL_checkstack(L, 1, nostack); + lua_pushvalue(L, fn_idx); + cfg->error_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } + } + + return 0; +} diff --git a/components/modules/serial_common.h b/components/modules/serial_common.h new file mode 100644 index 00000000..43511715 --- /dev/null +++ b/components/modules/serial_common.h @@ -0,0 +1,91 @@ +#ifndef SERIAL_COMMON_H +#define SERIAL_COMMON_H + +#include "lua.h" + +#include +#include + +struct serial_input_cfg; +typedef struct serial_input_cfg serial_input_cfg_t; + +/** + * Instantiate a new serial_input object. + * @returns a freshly allocated serial input object, with no further resources + * associated with it. + */ +serial_input_cfg_t *serial_input_new(void); + +/** + * Free a serial_input_cfg_t object. + * Releases all associated resources. The object may not be passed to any + * serial_input_xxx functions after this. + * + * Must only be called from the Lua VM task context. + */ +void serial_input_free(lua_State *L, serial_input_cfg_t *cfg); + + +/** + * Helper function to hand registration of "data" and "error" callbacks. + * Expects the following calling signature: + * on("method", [number/char], function) + * + * Must only be called from the Lua VM task context. + * + * @param L The current Lua VM. + * @param cfg Instance to un/register with. Must've been initialised with + * @c serial_input_init() originally. + * @return Zero. Will luaL_error() on invalid args. + */ +int serial_input_register(lua_State *L, serial_input_cfg_t *cfg); + +/** + * Feed data into a serial_input stream for processing. + * + * Must only be called from the Lua VM task context, as it will invoke + * Lua callbacks as necessary. + * Uses lua_getstate() to obtain the LVM instance. + * + * @param cfg The serial_input instance. + * @param buf The data buffer from which to feed bytes. + * @param len The number of bytes available in the buffer. + */ +void serial_input_feed_data(serial_input_cfg_t *cfg, const char *buf, size_t len); + +/** + * Checks whether a "data" callback is registered. + * + * @param cfg The serial_input instance. + * @return Whether a "data" callback is currently registered. + */ +bool serial_input_has_data_cb(serial_input_cfg_t *cfg); + +/** + * Direct access to invoking a configured "data" callback. + * + * Must only be called from the Lua VM task context. + * Uses lua_getstate() to obtain the LVM instance. + * + * @param cfg The serial_input instance. + * @param buf The data which to pass to the callback. + * @param len The number of bytes available in the buffer. + * @return True if the callback was successfully invoked (registered, and valid + * non-empty data passed). + */ +bool serial_input_dispatch_data(serial_input_cfg_t *cfg, const char *buf, size_t len); + +/** + * Direct access to invoking a configured "error" callback. + * Must only be called from the Lua VM task context. + * Uses lua_getstate() to obtain the LVM instance. + * + * @param cfg The serial_input instance. + * @param msg The message to pass to the error callback. + * @param len The length of the message, in bytes. + * @return True if the callback was successfully invoked (registered, and valid + * non-empty data passed). + */ +bool serial_input_report_error(serial_input_cfg_t *cfg, const char *msg, size_t len); + +#endif diff --git a/components/modules/uart.c b/components/modules/uart.c index 6a738f0a..6414e4a2 100644 --- a/components/modules/uart.c +++ b/components/modules/uart.c @@ -3,61 +3,23 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "serial_common.h" #include "linput.h" #include "lmem.h" #include #include -typedef struct { - int receive_rf; - int error_rf; - char *line_buffer; - size_t line_position; - uint16_t need_len; - int16_t end_char; -} uart_cb_cfg_t; - -static lua_State *gL = NULL; -static uart_cb_cfg_t uart_cb_cfg[NUM_UART]; - - -static bool uart_on_data_cb(unsigned id, const char *buf, size_t len){ - if(!buf || len==0) - return false; - if(uart_cb_cfg[id].receive_rf == LUA_NOREF) - return false; - if(!gL) - return false; - - int top = lua_gettop(gL); - lua_rawgeti(gL, LUA_REGISTRYINDEX, uart_cb_cfg[id].receive_rf); - lua_pushlstring(gL, buf, len); - luaL_pcallx(gL, 1, 0); - lua_settop(gL, top); - return !run_input; -} +static serial_input_cfg_t *uart_cb_cfg[NUM_UART]; bool uart_on_error_cb(unsigned id, const char *buf, size_t len){ - if(!buf || len==0) - return false; - if(uart_cb_cfg[id].error_rf == LUA_NOREF) - return false; - if(!gL) - return false; - - int top = lua_gettop(gL); - lua_rawgeti(gL, LUA_REGISTRYINDEX, uart_cb_cfg[id].error_rf); - lua_pushlstring(gL, buf, len); - luaL_pcallx(gL, 1, 0); - lua_settop(gL, top); - return true; + return serial_input_report_error(uart_cb_cfg[id], buf, len); } bool uart_has_on_data_cb(unsigned id){ - return uart_cb_cfg[id].receive_rf != LUA_NOREF; + return serial_input_has_data_cb(uart_cb_cfg[id]); } @@ -66,116 +28,43 @@ void uart_feed_data(unsigned id, const char *buf, size_t len) if (id >= NUM_UART) return; - uart_cb_cfg_t *cfg = &uart_cb_cfg[id]; - if (!cfg->line_buffer) - return; + serial_input_feed_data(uart_cb_cfg[id], buf, len); +} - for (unsigned i = 0; i < len; ++i) - { - char ch = buf[i]; - cfg->line_buffer[cfg->line_position] = ch; - cfg->line_position++; - uint16_t need_len = cfg->need_len; - int16_t end_char = cfg->end_char; - size_t max_wanted = - (end_char >= 0 && need_len == 0) ? LUA_MAXINPUT : need_len; - bool at_end = (cfg->line_position >= max_wanted); - bool end_char_found = - (end_char >= 0 && (uint8_t)ch == (uint8_t)end_char); - if (at_end || end_char_found) { - uart_on_data_cb(id, cfg->line_buffer, cfg->line_position); - cfg->line_position = 0; - } - } +static int ensure_valid_id(lua_State *L, int id) +{ + MOD_CHECK_ID(uart, id); + + int console = -1; +#if defined(CONFIG_ESP_CONSOLE_UART_DEFAULT) || defined(CONFIG_ESP_CONSOLE_UART_CUSTOM) + console = CONFIG_ESP_CONSOLE_UART_NUM; +#endif + + if (id == console) + return luaL_error(L, + "uart in use by system console; use the 'console' module instead"); + + return 0; } // Lua: uart.on([id], "method", [number/char], function, [run_input]) static int uart_on( lua_State* L ) { - unsigned id = CONFIG_ESP_CONSOLE_UART_NUM; - size_t sl, el; - int32_t run = 1; - uint8_t stack = 1; - const char *method; - - if( lua_isnumber( L, stack ) ) { - id = ( unsigned )luaL_checkinteger( L, stack ); - MOD_CHECK_ID( uart, id ); - stack++; - } - - uart_cb_cfg_t *cfg = &uart_cb_cfg[id]; - - method = luaL_checklstring( L, stack, &sl ); - stack++; - if (method == NULL) - return luaL_error( L, "wrong arg type" ); - - if( lua_type( L, stack ) == LUA_TNUMBER ) + int id = 0; + if (lua_isnumber(L, 1)) { - cfg->need_len = (uint16_t)luaL_checkinteger(L, stack); - stack++; - cfg->end_char = -1; - if(cfg->need_len > 255) - { - cfg->need_len = 255; - return luaL_error( L, "wrong arg range" ); - } - } - else if(lua_isstring(L, stack)) - { - const char *end = luaL_checklstring( L, stack, &el ); - stack++; - if(el!=1){ - return luaL_error( L, "wrong arg range" ); - } - cfg->end_char = (int16_t)end[0]; - cfg->need_len = 0; + id = luaL_checkinteger(L, 1); + lua_remove(L, 1); } - if (lua_isfunction(L, stack)) { - if ( lua_isnumber(L, stack+1) ){ - run = lua_tointeger(L, stack+1); - } - lua_pushvalue(L, stack); // copy argument (func) to the top of stack - } else { - lua_pushnil(L); - } - if(sl == 4 && strcmp(method, "data") == 0){ - if(id == CONFIG_ESP_CONSOLE_UART_NUM) - run_input = true; - if(cfg->receive_rf != LUA_NOREF){ - luaL_unref(L, LUA_REGISTRYINDEX, cfg->receive_rf); - cfg->receive_rf = LUA_NOREF; - } - if(!lua_isnil(L, -1)){ - cfg->receive_rf = luaL_ref(L, LUA_REGISTRYINDEX); - gL = L; - if(id == CONFIG_ESP_CONSOLE_UART_NUM && run==0) - run_input = false; - } else { - lua_pop(L, 1); - } - } else if(sl == 5 && strcmp(method, "error") == 0){ - if(cfg->error_rf != LUA_NOREF){ - luaL_unref(L, LUA_REGISTRYINDEX, cfg->error_rf); - cfg->error_rf = LUA_NOREF; - } - if(!lua_isnil(L, -1)){ - cfg->error_rf = luaL_ref(L, LUA_REGISTRYINDEX); - gL = L; - } else { - lua_pop(L, 1); - } - } else { - lua_pop(L, 1); - return luaL_error( L, "method not supported" ); - } - return 0; + ensure_valid_id(L, id); + + return serial_input_register(L, uart_cb_cfg[id]); } + // Lua: actualbaud = setup( id, baud, databits, parity, stopbits, echo ) static int uart_setup( lua_State* L ) { @@ -186,40 +75,36 @@ static int uart_setup( lua_State* L ) memset(&pins, 0, sizeof(pins)); id = luaL_checkinteger( L, 1 ); - MOD_CHECK_ID( uart, id ); + ensure_valid_id(L, id); baud = luaL_checkinteger( L, 2 ); databits = luaL_checkinteger( L, 3 ); parity = luaL_checkinteger( L, 4 ); stopbits = luaL_checkinteger( L, 5 ); if (!lua_isnoneornil(L, 6)) { - if(id == CONFIG_ESP_CONSOLE_UART_NUM){ - input_echo = luaL_checkinteger(L, 6) > 0; - } else { - luaL_checktable(L, 6); + luaL_checktable(L, 6); - lua_getfield (L, 6, "tx"); - pins.tx_pin = luaL_checkint(L, -1); - lua_getfield (L, 6, "rx"); - pins.rx_pin = luaL_checkint(L, -1); - lua_getfield (L, 6, "cts"); - pins.cts_pin = luaL_optint(L, -1, -1); - lua_getfield (L, 6, "rts"); - pins.rts_pin = luaL_optint(L, -1, -1); - - lua_getfield (L, 6, "tx_inverse"); - pins.tx_inverse = lua_toboolean(L, -1); - lua_getfield (L, 6, "rx_inverse"); - pins.rx_inverse = lua_toboolean(L, -1); - lua_getfield (L, 6, "cts_inverse"); - pins.cts_inverse = lua_toboolean(L, -1); - lua_getfield (L, 6, "rts_inverse"); - pins.rts_inverse = lua_toboolean(L, -1); - - lua_getfield (L, 6, "flow_control"); - pins.flow_control = luaL_optint(L, -1, PLATFORM_UART_FLOW_NONE); + lua_getfield (L, 6, "tx"); + pins.tx_pin = luaL_checkint(L, -1); + lua_getfield (L, 6, "rx"); + pins.rx_pin = luaL_checkint(L, -1); + lua_getfield (L, 6, "cts"); + pins.cts_pin = luaL_optint(L, -1, -1); + lua_getfield (L, 6, "rts"); + pins.rts_pin = luaL_optint(L, -1, -1); - pins_to_use = &pins; - } + lua_getfield (L, 6, "tx_inverse"); + pins.tx_inverse = lua_toboolean(L, -1); + lua_getfield (L, 6, "rx_inverse"); + pins.rx_inverse = lua_toboolean(L, -1); + lua_getfield (L, 6, "cts_inverse"); + pins.cts_inverse = lua_toboolean(L, -1); + lua_getfield (L, 6, "rts_inverse"); + pins.rts_inverse = lua_toboolean(L, -1); + + lua_getfield (L, 6, "flow_control"); + pins.flow_control = luaL_optint(L, -1, PLATFORM_UART_FLOW_NONE); + + pins_to_use = &pins; } res = platform_uart_setup( id, baud, databits, parity, stopbits, pins_to_use ); @@ -232,7 +117,7 @@ static int uart_setmode(lua_State* L) unsigned id, mode; id = luaL_checkinteger( L, 1 ); - MOD_CHECK_ID( uart, id ); + ensure_valid_id(L, id); mode = luaL_checkinteger( L, 2 ); platform_uart_setmode(id, mode); @@ -249,7 +134,7 @@ static int uart_write( lua_State* L ) int total = lua_gettop( L ), s; id = luaL_checkinteger( L, 1 ); - MOD_CHECK_ID( uart, id ); + ensure_valid_id(L, id); for( s = 2; s <= total; s ++ ) { if( lua_type( L, s ) == LUA_TNUMBER ) @@ -275,13 +160,8 @@ static int uart_stop( lua_State* L ) { unsigned id; id = luaL_checkinteger( L, 1 ); - MOD_CHECK_ID( uart, id ); + ensure_valid_id(L, id); platform_uart_stop( id ); - if (uart_cb_cfg[id].line_buffer) - { - luaM_freemem(L, uart_cb_cfg[id].line_buffer, LUA_MAXINPUT); - uart_cb_cfg[id].line_buffer = NULL; - } return 0; } @@ -291,9 +171,7 @@ static int uart_start( lua_State* L ) unsigned id; int err; id = luaL_checkinteger( L, 1 ); - MOD_CHECK_ID( uart, id ); - if (!uart_cb_cfg[id].line_buffer) - uart_cb_cfg[id].line_buffer = luaM_malloc(L, LUA_MAXINPUT); + ensure_valid_id(L, id); err = platform_uart_start( id ); lua_pushboolean( L, err == 0 ); return 1; @@ -303,7 +181,7 @@ static int uart_getconfig(lua_State* L) { uint32_t id, baud, databits, parity, stopbits; id = luaL_checkinteger(L, 1); - MOD_CHECK_ID(uart, id); + ensure_valid_id(L, id); int err = platform_uart_get_config(id, &baud, &databits, &parity, &stopbits); if (err) { @@ -320,7 +198,7 @@ static int uart_getconfig(lua_State* L) { static int uart_wakeup (lua_State *L) { uint32_t id = luaL_checkinteger(L, 1); - MOD_CHECK_ID(uart, id); + ensure_valid_id(L, id); int threshold = luaL_checkinteger(L, 2); int err = platform_uart_set_wakeup_threshold(id, threshold); if (err) { @@ -332,7 +210,7 @@ static int uart_wakeup (lua_State *L) static int luart_tx_flush (lua_State *L) { uint32_t id = luaL_checkinteger(L, 1); - MOD_CHECK_ID(uart, id); + ensure_valid_id(L, id); platform_uart_flush(id); return 0; } @@ -367,13 +245,9 @@ LROT_END(uart, NULL, 0) int luaopen_uart( lua_State *L ) { for(int id = 0; id < sizeof(uart_cb_cfg)/sizeof(uart_cb_cfg[0]); id++) { - uart_cb_cfg_t *cfg = &uart_cb_cfg[id]; - cfg->receive_rf = LUA_NOREF; - cfg->error_rf = LUA_NOREF; - cfg->line_buffer = NULL; - cfg->line_position = 0; - cfg->need_len = 0; - cfg->end_char = -1; + uart_cb_cfg[id] = serial_input_new(); + if (!uart_cb_cfg[id]) + return luaL_error(L, "out of mem"); } return 0; } diff --git a/components/platform/platform.c b/components/platform/platform.c index 86606df8..f5860bbd 100644 --- a/components/platform/platform.c +++ b/components/platform/platform.c @@ -88,14 +88,6 @@ void uart_event_task( task_param_t param, task_prio_t prio ) { unsigned id = post->id; xSemaphoreGive(sem); if(post->type == PLATFORM_UART_EVENT_DATA) { - if (id == CONFIG_ESP_CONSOLE_UART_NUM && run_input) { - size_t i = 0; - while (i < post->size) - { - unsigned used = feed_lua_input(post->data + i, post->size - i); - i += used; - } - } if (uart_has_on_data_cb(id)) uart_feed_data(id, post->data, post->size); @@ -206,6 +198,10 @@ static void task_uart( void *pvParameters ){ // pins must not be null for non-console uart uint32_t platform_uart_setup( unsigned id, uint32_t baud, int databits, int parity, int stopbits, uart_pins_t* pins ) { +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + if (id == CONFIG_ESP_CONSOLE_UART_NUM) + return 0; +#endif int flow_control = UART_HW_FLOWCTRL_DISABLE; if (pins != NULL) { if(pins->flow_control & PLATFORM_UART_FLOW_CTS) flow_control |= UART_HW_FLOWCTRL_CTS; @@ -286,30 +282,29 @@ void platform_uart_setmode(unsigned id, unsigned mode) void platform_uart_send_multi( unsigned id, const char *data, size_t len ) { - size_t i; - if (id == CONFIG_ESP_CONSOLE_UART_NUM) { - for( i = 0; i < len; i ++ ) { - putchar (data[ i ]); - } - } else { - uart_write_bytes(id, data, len); - } +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + if (id == CONFIG_ESP_CONSOLE_UART_NUM) + return; +#endif + uart_write_bytes(id, data, len); } void platform_uart_send( unsigned id, uint8_t data ) { +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM if (id == CONFIG_ESP_CONSOLE_UART_NUM) - putchar (data); - else - uart_write_bytes(id, (const char *)&data, 1); + return; +#endif + uart_write_bytes(id, (const char *)&data, 1); } void platform_uart_flush( unsigned id ) { +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM if (id == CONFIG_ESP_CONSOLE_UART_NUM) - fflush (stdout); - else - uart_tx_flush(id); + return; +#endif + uart_tx_flush(id); } @@ -354,9 +349,12 @@ void platform_uart_stop( unsigned id ) } int platform_uart_get_config(unsigned id, uint32_t *baudp, uint32_t *databitsp, uint32_t *parityp, uint32_t *stopbitsp) { - int err; +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + if (id == CONFIG_ESP_CONSOLE_UART_NUM) + return -1; +#endif - err = uart_get_baudrate(id, baudp); + int err = uart_get_baudrate(id, baudp); if (err != ESP_OK) return -1; *baudp &= 0xFFFFFFFE; // round down @@ -405,6 +403,10 @@ int platform_uart_get_config(unsigned id, uint32_t *baudp, uint32_t *databitsp, int platform_uart_set_wakeup_threshold(unsigned id, unsigned threshold) { +#if CONFIG_ESP_CONSOLE_UART_DEFAULT || CONFIG_ESP_CONSOLE_UART_CUSTOM + if (id == CONFIG_ESP_CONSOLE_UART_NUM) + return -1; +#endif esp_err_t err = uart_set_wakeup_threshold(id, threshold); return (err == ESP_OK) ? 0 : -1; } diff --git a/docs/modules/console.md b/docs/modules/console.md new file mode 100644 index 00000000..d364acaa --- /dev/null +++ b/docs/modules/console.md @@ -0,0 +1,88 @@ +# Console Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2024-10-22 | [jmattsson](https://github.com/jmattsson) | [jmattsson](https://github.com/jmattsson) | [console.c](../../components/modules/console.c)| + +The `console` modules allows direct access to the system console. The system +console has typically been a UART, but by now several options are available +across the ESP32 range of SoCs, including UART, USB-Serial-JTAG and USB CDC-ACM. + +By default the system console is linked up to provide an interactive Lua +shell (REPL — Read-Execute-Print Loop). It also provides a hook for listening +in on the data received on the console programatically, and the interactivity +may also be disabled (and re-enabled) programatically if so desired. + +## console.on() + +Used to register or deregister a callback function to handle console events. + +#### Syntax +`console.on(method, [number/end_char], [function])` + +#### Parameters +- `method`. One of + - "data" for bytes received on the console + - "error" if an error condition is encountered on the console +- `number/end_char`. Only for event `data`. + - if pass in a number n, the callback will called when n chars are received. + - if n=0, will receive every char in buffer. + - if pass in a one char string "c", the callback will called when "c" is encounterd, or max n=255 received. +- `function` callback function. + - event "data" has a callback like this: `function(data) end` + - event "error" has a callback like this: `function(err) end` + +To unregister the callback, specify `nil` as the function. + +#### Returns +`nil` + +#### Example +```lua +-- when 4 chars is received. +console.on("data", 4, + function(data) + print("received from console:", data) + if data=="quit" then + console.on("data", 0, nil) + end +end) +-- when '\r' is received. +console.on("data", "\r", + function(data) + print("received from console:", data) + if data=="quit\r" then + console.on("data", 0, nil) + end +end) + +-- error handler +console.on("error", + function(err) + print("error on console:", err) + end) +``` + +## console.mode() + +Controls the interactivity of the console. + +#### Syntax +`console.mode(mode)` + +#### Parameters +- `mode` One of + - `console.INTERACTIVE` automatically pass console data to the Lua VM + for execution. This is the default mode. + - `console.NONINTERACTIVE` disables the automatic passing of console data + to the Lua VM. The data only goes to the registered "data" callback, + if any. + +#### Returns +`nil` + +#### Example +```lua +-- Implement a REL instead of the usual REPL +console.on("data", "\r", function(line) node.input(line.."\r\n") end) +console.mode(console.NONINTERACTIVE) +``` diff --git a/docs/modules/uart.md b/docs/modules/uart.md index c31905a4..0110433e 100644 --- a/docs/modules/uart.md +++ b/docs/modules/uart.md @@ -5,31 +5,31 @@ The [UART](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver/transmitter) (Universal asynchronous receiver/transmitter) module allows configuration of and communication over the UART serial port. -The default setup for the console uart is controlled by build-time settings. The default uart for console is `UART0`. The default rate is 115,200 bps. In addition, auto-baudrate detection is enabled for the first two minutes -after platform boot. This will cause a switch to the correct baud rate once a few characters are received. Auto-baudrate detection is disabled when `uart.setup` is called. +If the UART is in use as the system console, it is unavailable for use by this +module. Instead, refer to the `console` module. -For other uarts, you should call `uart.setup` and `uart.start` to get them working. +Before using a UART, you must call `uart.setup` and `uart.start` to set them up. ## uart.on() -Sets the callback function to handle UART events. +Sets the callback function to handle UART events. For a UART used by the +console, refer to the `console` module instead. #### Syntax -`uart.on([id], method, [number/end_char], [function], [run_input])` +`uart.on([id], method, [number/end_char], [function])` #### Parameters -- `id` uart id, default value is uart num of the console. +- `id` uart id, except console uart. Default value is uart 0. - `method` "data", data has been received on the UART. "error", error occurred on the UART. - `number/end_char`. Only for event `data`. - - if pass in a number n<255, the callback will called when n chars are received. + - if pass in a number n, the callback will called when n chars are received. - if n=0, will receive every char in buffer. - if pass in a one char string "c", the callback will called when "c" is encounterd, or max n=255 received. - `function` callback function. - event "data" has a callback like this: `function(data) end` - event "error" has a callback like this: `function(err) end`. `err` could be one of "out_of_memory", "break", "rx_error". -- `run_input` 0 or 1. Only for "data" event on console uart. If 0, input from UART will not go into Lua interpreter, can accept binary data. If 1, input from UART will go into Lua interpreter, and run. -To unregister the callback, provide only the "data" parameter. +To unregister the callback, provide only the "method" parameter. #### Returns `nil` @@ -71,17 +71,16 @@ uart.on(2, "error", (Re-)configures the communication parameters of the UART. #### Syntax -`uart.setup(id, baud, databits, parity, stopbits, echo_or_pins)` +`uart.setup(id, baud, databits, parity, stopbits, pins)` #### Parameters -- `id` uart id +- `id` uart id, except console uart - `baud` one of 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 74880, 115200, 230400, 256000, 460800, 921600, 1843200, 3686400 - `databits` one of 5, 6, 7, 8 - `parity` `uart.PARITY_NONE`, `uart.PARITY_ODD`, or `uart.PARITY_EVEN` - `stopbits` `uart.STOPBITS_1`, `uart.STOPBITS_1_5`, or `uart.STOPBITS_2` -- `echo_or_pins` - - for console uart, this should be a int. if 0, disable echo, otherwise enable echo - - for others, this is a table: +- `pins` + - table with the following entries: - `tx` int. TX pin. Required - `rx` int. RX pin. Required - `cts` in. CTS pin. Optional @@ -113,7 +112,7 @@ Returns the current configuration parameters of the UART. `uart.getconfig(id)` #### Parameters -- `id` UART id (0 or 1). +- `id` uart id, except console uart #### Returns Four values as follows: @@ -130,7 +129,7 @@ print (uart.getconfig(0)) ``` ## uart.start() -Start the UART. You do not need to call `start()` on the console uart. +Start the UART. #### Syntax `uart.start(id)` @@ -143,7 +142,7 @@ Boolean. `true` if uart is started. ## uart.stop() -Stop the UART. You should not call `stop()` on the console uart. +Stop the UART. #### Syntax `uart.stop(id)` @@ -164,7 +163,7 @@ Set UART controllers communication mode `uart.setmode(id, mode)` #### Parameters -- `id` uart id +- `id` uart id, except console uart - `mode` value should be one of - `uart.MODE_UART` default UART mode, is set after uart.setup() call - `uart.MODE_RS485_COLLISION_DETECT` receiver must be always enabled, transmitter is automatically switched using RTS pin, collision is detected by UART hardware (note: no event is generated on collision, limitation of esp-idf) @@ -184,15 +183,15 @@ Wait for any data currently in the UART transmit buffers to be written out. It c `uart.txflush(id)` #### Parameters -- `id` uart id +- `id` uart id, except console uart #### Returns `nil` #### Example ```lua -print("I want this to show up now not in 5 seconds") -uart.txflush(0) -- assuming 0 is the console uart +uart.write(0, "I want this to show up now not in 5 seconds") +uart.txflush(0) node.sleep({secs=5}) ``` @@ -208,7 +207,7 @@ Configure the light sleep wakeup threshold. This is the number of positive edges `uart.wakeup(id, val)` #### Parameters -- `id` uart id +- `id` uart id, except console uart - `val` the new value #### Returns @@ -231,7 +230,7 @@ Write string or byte to the UART. `uart.write(id, data1 [, data2, ...])` #### Parameters -- `id` uart id +- `id` uart id, except console uart - `data1`... string or byte to send via UART #### Returns diff --git a/mkdocs.yml b/mkdocs.yml index 8682c4c9..01ea19c0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -43,6 +43,7 @@ nav: - 'bit': 'modules/bit.md' - 'bthci': 'modules/bthci.md' - 'can': 'modules/can.md' + - 'console': 'modules/console.md' - 'crypto': 'modules/crypto.md' - 'dac': 'modules/dac.md' - 'dht': 'modules/dht.md'