From 14c1b8fd542beabba68646dab93f4862b364a4b3 Mon Sep 17 00:00:00 2001 From: Terry Ellison Date: Thu, 18 Jul 2019 17:02:02 +0100 Subject: [PATCH 01/27] Lua 5.1 to 5.3 realignement phase 1 --- .gdbinitlua | 20 +- app/Makefile | 4 +- app/driver/Makefile | 2 +- app/driver/input.c | 196 +++++++++++ app/driver/pwm.c | 6 +- app/driver/readline.c | 111 ------ app/driver/rotary.c | 5 +- app/driver/spi.c | 19 +- app/driver/switec.c | 2 +- app/driver/uart.c | 39 ++- app/include/driver/input.h | 11 + app/include/driver/readline.h | 6 - app/include/driver/uart.h | 3 +- app/include/task/task.h | 47 +-- app/include/user_config.h | 4 +- app/lua/Makefile | 2 +- app/lua/lauxlib.c | 5 +- app/lua/lauxlib.h | 4 +- app/lua/lbaselib.c | 21 +- app/lua/ldblib.c | 4 +- app/lua/lflash.c | 30 +- app/lua/lnodemcu.c | 116 +++++++ app/lua/loadlib.c | 2 +- app/lua/lobject.h | 2 +- app/lua/lstate.c | 11 +- app/lua/lstate.h | 1 + app/lua/lua.c | 611 +++++++++++----------------------- app/lua/lua.h | 27 +- app/modules/gpio.c | 1 + app/modules/gpio_pulse.c | 8 +- app/modules/net.c | 22 +- app/modules/node.c | 114 +++---- app/modules/pipe.c | 286 ++++++++++++---- app/modules/somfy.c | 1 + app/modules/uart.c | 92 ++--- app/platform/platform.c | 110 +++++- app/platform/platform.h | 25 +- app/platform/vfs.h | 22 +- app/pm/swtimer.c | 1 + app/task/Makefile | 41 --- app/task/task.c | 72 ---- app/user/user_main.c | 56 +--- docs/modules/node.md | 45 +-- docs/modules/pipe.md | 6 +- 44 files changed, 1115 insertions(+), 1098 deletions(-) create mode 100644 app/driver/input.c delete mode 100644 app/driver/readline.c create mode 100644 app/include/driver/input.h delete mode 100644 app/include/driver/readline.h create mode 100644 app/lua/lnodemcu.c delete mode 100644 app/task/Makefile delete mode 100644 app/task/task.c diff --git a/.gdbinitlua b/.gdbinitlua index b832fab5f1..ef467354b6 100644 --- a/.gdbinitlua +++ b/.gdbinitlua @@ -3,9 +3,9 @@ set pagination off set print null-stop define prTS - set $o = &(((TString *)($arg0))->tsv) + set $o = &(((TString *)(($arg0).value))->tsv) printf "Common header: next = %p, marked = 0x%01x\n", $o->next, $o->marked - printf "String: hash = 0x%08x, len = %u : %s\n", $o->hash, $o->len, (char *)(&$o[1]) + printf "String: hash = 0x%08x, len = %u : %s\n", $o->hash, $o->len, (char *)($o+1) end define prTnodes @@ -24,6 +24,18 @@ define prTnodes set $i = $i +1 end end + +define prTarray + set $o = (Table *)($arg0) + set $n = $o->sizearray + set $i = 0 + while $i < $n + set $nd = ($o->array) + $i + prTV $nd + set $i = $i +1 + end +end + define prTV if $arg0 set $type = ($arg0).tt @@ -78,6 +90,10 @@ define prTV end if $type == 9 # UserData + set $o = &($val->gc.u.uv) + printf "Common header: next = %p, marked = 0x%01x\n", $o->next, $o->marked + printf "UD = %p Userdata: metatable = ", ($o+1)) + print ($o)->metatable end if $type == 10 # Thread diff --git a/app/Makefile b/app/Makefile index 4fab0eedaa..a419c52602 100644 --- a/app/Makefile +++ b/app/Makefile @@ -36,7 +36,6 @@ SUBDIRS= \ libc \ lua \ lwip \ - task \ smart \ modules \ spiffs \ @@ -64,8 +63,7 @@ COMPONENTS_eagle.app.v6 = \ user/libuser.a \ crypto/libcrypto.a \ driver/libdriver.a \ - platform/libplatform.a \ - task/libtask.a \ + platform/libplatform.a \ libc/liblibc.a \ lua/liblua.a \ lwip/liblwip.a \ diff --git a/app/driver/Makefile b/app/driver/Makefile index 5dbd685374..cee238339e 100644 --- a/app/driver/Makefile +++ b/app/driver/Makefile @@ -15,7 +15,7 @@ ifndef PDIR GEN_LIBS = libdriver.a endif -STD_CFLAGS=-std=gnu11 -Wimplicit +STD_CFLAGS=-std=gnu11 -Wimplicit -Wall ############################################################# # Configuration i.e. compile options etc. diff --git a/app/driver/input.c b/app/driver/input.c new file mode 100644 index 0000000000..79a522f8ef --- /dev/null +++ b/app/driver/input.c @@ -0,0 +1,196 @@ +#include "platform.h" +#include "driver/uart.h" +#include "driver/input.h" +#include +#include "mem.h" + +/**DEBUG**/extern void dbg_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); + +static void input_handler(platform_task_param_t flag, uint8 priority); + +static struct input_state { + char *data; + int line_pos; + size_t len; + const char *prompt; + uart_cb_t uart_cb; + platform_task_handle_t input_sig; + int data_len; + bool run_input; + bool uart_echo; + char last_char; + char end_char; + uint8 input_sig_flag; +} ins = {0}; + +#define NUL '\0' +#define BS '\010' +#define CR '\r' +#define LF '\n' +#define DEL 0x7f +#define BS_OVER "\010 \010" + +#define sendStr(s) uart0_sendStr(s) +#define putc(c) uart0_putc(c) + +// UartDev is defined and initialized in rom code. +extern UartDevice UartDev; + +static bool uart_getc(char *c){ + RcvMsgBuff *pRxBuff = &(UartDev.rcv_buff); + if(pRxBuff->pWritePos == pRxBuff->pReadPos){ // empty + return false; + } + // ETS_UART_INTR_DISABLE(); + ETS_INTR_LOCK(); + *c = (char)*(pRxBuff->pReadPos); + if (pRxBuff->pReadPos == (pRxBuff->pRcvMsgBuff + RX_BUFF_SIZE)) { + pRxBuff->pReadPos = pRxBuff->pRcvMsgBuff ; + } else { + pRxBuff->pReadPos++; + } + // ETS_UART_INTR_ENABLE(); + ETS_INTR_UNLOCK(); + return true; +} + +/* +** input_handler at high-priority is a system post task used to process pending Rx +** data on UART0. The flag is used as a latch to stop the interrupt handler posting +** multiple pending requests. At low priority it is used the trigger interactive +** compile. +** +** The ins.data check detects up the first task call which used to initialise +** everything. +*/ +int lua_main (void); +static bool input_readline(void); + +static void input_handler(platform_task_param_t flag, uint8 priority) { + (void) priority; + if (!ins.data) { + lua_main(); + return; + } + ins.input_sig_flag = flag & 0x1; + while (input_readline()) {} +} + +/* +** The input state (ins) is private, so input_setup() exposes the necessary +** access to public properties and is called in user_init() before the Lua +** enviroment is initialised. The second routine input_setup_receive() is +** called in lua.c after the Lua environment is available to bind the Lua +** input handler. Any UART input before this receive setup is ignored. +*/ +void input_setup(int bufsize, const char *prompt) { + // Initialise non-zero elements + ins.run_input = true; + ins.uart_echo = true; + ins.data = os_malloc(bufsize); + ins.len = bufsize; + ins.prompt = prompt; + ins.input_sig = platform_task_get_id(input_handler); + // pass the task CB parameters to the uart driver + uart_init_task(ins.input_sig, &ins.input_sig_flag); + ETS_UART_INTR_ENABLE(); +} + +void input_setup_receive(uart_cb_t uart_on_data_cb, int data_len, char end_char, bool run_input) { + ins.uart_cb = uart_on_data_cb; + ins.data_len = data_len; + ins.end_char = end_char; + ins.run_input = run_input; +} + +void input_setecho (bool flag) { + ins.uart_echo = flag; +} + +void input_setprompt (const char *prompt) { + ins.prompt = prompt; +} + +/* +** input_readline() is called from the input_handler() event routine which is +** posted by the UART Rx ISR posts. This works in one of two modes depending on +** the bool ins.run_input. +** - TRUE: it clears the UART FIFO up to EOL, doing any callback and sending +** the line to Lua. +** - FALSE: it clears the UART FIFO doing callbacks according to the data_len / +** end_char break. +*/ +void lua_input_string (const char *line, int len); + +static bool input_readline(void) { + char ch = NUL; + if (ins.run_input) { + while (uart_getc(&ch)) { + /* handle CR & LF characters and aggregate \n\r and \r\n pairs */ + if ((ch == CR && ins.last_char == LF) || + (ch == LF && ins.last_char == CR)) { + ins.last_char = NUL; + continue; + } + + /* backspace key */ + if (ch == DEL || ch == BS) { + if (ins.line_pos > 0) { + if(ins.uart_echo) sendStr(BS_OVER); + ins.line_pos--; + } + ins.data[ins.line_pos] = 0; + ins.last_char = NUL; + continue; + } + ins.last_char = ch; + + /* end of data */ + if (ch == CR || ch == LF) { + if (ins.uart_echo) putc(LF); + if (ins.uart_cb) ins.uart_cb(ins.data, ins.line_pos); + if (ins.line_pos == 0) { + /* Get a empty data, then go to get a new data */ + + sendStr(ins.prompt); + continue; + } else { + ins.data[ins.line_pos++] = LF; + lua_input_string(ins.data, ins.line_pos); + ins.line_pos = 0; + return true; + } + } + + if(ins.uart_echo) putc(ch); + + /* it's a large data, discard it */ + if ( ins.line_pos + 1 >= ins.len ){ + ins.line_pos = 0; + } + ins.data[ins.line_pos++] = ch; + } + + } else { + + if (!ins.uart_cb) { + while (uart_getc(&ch)) {} + } else if (ins.data_len == 0) { + while (uart_getc(&ch)) { + ins.uart_cb(&ch, 1); + } + } else { + while (uart_getc(&ch)) { + ins.data[ins.line_pos++] = ch; + if( ins.line_pos >= ins.len || + (ins.data_len > 0 && ins.line_pos >= ins.data_len) || + ch == ins.end_char ) { + ins.uart_cb(ins.data, ins.line_pos); + ins.line_pos = 0; + } + } + } + } + return false; +} + diff --git a/app/driver/pwm.c b/app/driver/pwm.c index 6422cc92e7..75b5d068aa 100644 --- a/app/driver/pwm.c +++ b/app/driver/pwm.c @@ -20,7 +20,7 @@ #include "driver/pwm.h" // #define PWM_DBG os_printf -#define PWM_DBG +#define PWM_DBG( ... ) // Enabling the next line will cause the interrupt handler to toggle // this output pin during processing so that the timing is obvious @@ -253,7 +253,7 @@ pwm_set_freq(uint16 freq, uint8 channel) pwm.period = PWM_1S / pwm.freq; } - +#if 0 /****************************************************************************** * FunctionName : pwm_set_freq_duty * Description : set pwm frequency and each channel's duty @@ -274,7 +274,7 @@ pwm_set_freq_duty(uint16 freq, uint16 *duty) pwm_set_duty(duty[i], pwm_out_io_num[i]); } } - +#endif /****************************************************************************** * FunctionName : pwm_get_duty * Description : get duty of each channel diff --git a/app/driver/readline.c b/app/driver/readline.c deleted file mode 100644 index 99192bbc85..0000000000 --- a/app/driver/readline.c +++ /dev/null @@ -1,111 +0,0 @@ -#include "ets_sys.h" -#include "os_type.h" -#include "osapi.h" -#include "driver/uart.h" -#include - -LOCAL os_timer_t readline_timer; - -// UartDev is defined and initialized in rom code. -extern UartDevice UartDev; - -#define uart_putc uart0_putc - -bool uart_getc(char *c){ - RcvMsgBuff *pRxBuff = &(UartDev.rcv_buff); - if(pRxBuff->pWritePos == pRxBuff->pReadPos){ // empty - return false; - } - // ETS_UART_INTR_DISABLE(); - ETS_INTR_LOCK(); - *c = (char)*(pRxBuff->pReadPos); - if (pRxBuff->pReadPos == (pRxBuff->pRcvMsgBuff + RX_BUFF_SIZE)) { - pRxBuff->pReadPos = pRxBuff->pRcvMsgBuff ; - } else { - pRxBuff->pReadPos++; - } - // ETS_UART_INTR_ENABLE(); - ETS_INTR_UNLOCK(); - return true; -} - -#if 0 -int readline4lua(const char *prompt, char *buffer, int length){ - char ch; - int line_position; - -start: - /* show prompt */ - uart0_sendStr(prompt); - - line_position = 0; - os_memset(buffer, 0, length); - while (1) - { - while (uart_getc(&ch)) - { - /* handle CR key */ - if (ch == '\r') - { - char next; - if (uart_getc(&next)) - ch = next; - } - /* backspace key */ - else if (ch == 0x7f || ch == 0x08) - { - if (line_position > 0) - { - uart_putc(0x08); - uart_putc(' '); - uart_putc(0x08); - line_position--; - } - buffer[line_position] = 0; - continue; - } - /* EOF(ctrl+d) */ - else if (ch == 0x04) - { - if (line_position == 0) - /* No input which makes lua interpreter close */ - return 0; - else - continue; - } - - /* end of line */ - if (ch == '\r' || ch == '\n') - { - buffer[line_position] = 0; - uart_putc('\n'); - if (line_position == 0) - { - /* Get a empty line, then go to get a new line */ - goto start; - } - else - { - return line_position; - } - } - - /* other control character or not an acsii character */ - if (ch < 0x20 || ch >= 0x80) - { - continue; - } - - /* echo */ - uart_putc(ch); - buffer[line_position] = ch; - ch = 0; - line_position++; - - /* it's a large line, discard it */ - if (line_position >= length) - line_position = 0; - } - } -} -#endif diff --git a/app/driver/rotary.c b/app/driver/rotary.c index c54bda555e..5b8eb5eea1 100644 --- a/app/driver/rotary.c +++ b/app/driver/rotary.c @@ -14,9 +14,9 @@ #include #include #include +#include "task/task.h" #include "driver/rotary.h" #include "user_interface.h" -#include "task/task.h" #include "ets_sys.h" // @@ -37,7 +37,7 @@ #define GET_READ_STATUS(d) (d->queue[d->read_offset & (QUEUE_SIZE - 1)]) #define ADVANCE_IF_POSSIBLE(d) if (d->read_offset < d->write_offset) { d->read_offset++; } -#define STATUS_IS_PRESSED(x) ((x & 0x80000000) != 0) +#define STATUS_IS_PRESSED(x) (((x) & 0x80000000) != 0) typedef struct { int8_t phase_a_pin; @@ -213,7 +213,6 @@ int rotary_setup(uint32_t channel, int phase_a, int phase_b, int press, task_han } data[channel] = d; - int i; d->tasknumber = tasknumber; diff --git a/app/driver/spi.c b/app/driver/spi.c index 2cd05eee04..32abf114c0 100644 --- a/app/driver/spi.c +++ b/app/driver/spi.c @@ -15,7 +15,6 @@ static uint32_t spi_clkdiv[2]; *******************************************************************************/ void spi_lcd_mode_init(uint8 spi_no) { - uint32 regvalue; if(spi_no>1) return; //handle invalid input number //bit9 of PERIPHS_IO_MUX should be cleared when HSPI clock doesn't equal CPU clock //bit8 of PERIPHS_IO_MUX should be cleared when SPI clock doesn't equal CPU clock @@ -112,8 +111,6 @@ uint32_t spi_set_clkdiv(uint8 spi_no, uint32_t clock_div) *******************************************************************************/ void spi_master_init(uint8 spi_no, unsigned cpol, unsigned cpha, uint32_t clock_div) { - uint32 regvalue; - if(spi_no>1) return; //handle invalid input number SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_CS_SETUP|SPI_CS_HOLD|SPI_RD_BYTE_ORDER|SPI_WR_BYTE_ORDER); @@ -258,7 +255,7 @@ void spi_mast_set_mosi(uint8 spi_no, uint16 offset, uint8 bitlen, uint32 data) } shift = 64 - (offset & 0x1f) - bitlen; - spi_buf.dword &= ~((1ULL << bitlen)-1 << shift); + spi_buf.dword &= ~(((1ULL << bitlen)-1) << shift); spi_buf.dword |= (uint64)data << shift; if (wn < 15) { @@ -344,7 +341,7 @@ void spi_mast_transaction(uint8 spi_no, uint8 cmd_bitlen, uint16 cmd_data, uint8 uint16 cmd = cmd_data << (16 - cmd_bitlen); // align to MSB cmd = (cmd >> 8) | (cmd << 8); // swap byte order WRITE_PERI_REG(SPI_USER2(spi_no), - ((cmd_bitlen - 1 & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | + (((cmd_bitlen - 1) & SPI_USR_COMMAND_BITLEN) << SPI_USR_COMMAND_BITLEN_S) | (cmd & SPI_USR_COMMAND_VALUE)); SET_PERI_REG_MASK(SPI_USER(spi_no), SPI_USR_COMMAND); } @@ -387,8 +384,6 @@ void spi_mast_transaction(uint8 spi_no, uint8 cmd_bitlen, uint16 cmd_data, uint8 *******************************************************************************/ void spi_byte_write_espslave(uint8 spi_no,uint8 data) { - uint32 regvalue; - if(spi_no>1) return; //handle invalid input number while(READ_PERI_REG(SPI_CMD(spi_no))&SPI_USR); @@ -413,8 +408,6 @@ void spi_byte_write_espslave(uint8 spi_no,uint8 data) *******************************************************************************/ void spi_byte_read_espslave(uint8 spi_no,uint8 *data) { - uint32 regvalue; - if(spi_no>1) return; //handle invalid input number while(READ_PERI_REG(SPI_CMD(spi_no))&SPI_USR); @@ -440,7 +433,7 @@ void spi_byte_write_espslave(uint8 spi_no,uint8 data) *******************************************************************************/ void spi_slave_init(uint8 spi_no) { - uint32 regvalue; +// uint32 regvalue; if(spi_no>1) return; //handle invalid input number @@ -565,7 +558,6 @@ void hspi_master_readwrite_repeat(void) #include "mem.h" static uint8 spi_data[32] = {0}; static uint8 idx = 0; -static uint8 spi_flg = 0; #define SPI_MISO #define SPI_QUEUE_LEN 8 os_event_t * spiQueue; @@ -596,9 +588,8 @@ void ICACHE_FLASH_ATTR void spi_slave_isr_handler(void *para) { - uint32 regvalue,calvalue; - static uint8 state =0; - uint32 recv_data,send_data; + uint32 regvalue; + uint32 recv_data; if(READ_PERI_REG(0x3ff00020)&BIT4){ //following 3 lines is to clear isr signal diff --git a/app/driver/switec.c b/app/driver/switec.c index d776749aec..04242b594f 100644 --- a/app/driver/switec.c +++ b/app/driver/switec.c @@ -18,13 +18,13 @@ #include #include #include +#include "task/task.h" #include "driver/switec.h" #include "ets_sys.h" #include "os_type.h" #include "osapi.h" #include "hw_timer.h" #include "user_interface.h" -#include "task/task.h" #define N_STATES 6 // diff --git a/app/driver/uart.c b/app/driver/uart.c index 944e34000a..5b50d194b9 100644 --- a/app/driver/uart.c +++ b/app/driver/uart.c @@ -12,7 +12,7 @@ #include "ets_sys.h" #include "osapi.h" #include "driver/uart.h" -#include "task/task.h" +#include "platform.h" #include "user_config.h" #include "user_interface.h" #include "osapi.h" @@ -29,15 +29,15 @@ // For event signalling -static task_handle_t sig = 0; +static platform_task_handle_t sig = 0; static uint8 *sig_flag; static uint8 isr_flag = 0; // UartDev is defined and initialized in rom code. extern UartDevice UartDev; - +#ifdef BIT_RATE_AUTOBAUD static os_timer_t autobaud_timer; - +#endif static void (*alt_uart0_tx)(char txchar); LOCAL void ICACHE_RAM_ATTR @@ -165,7 +165,7 @@ uart_tx_one_char(uint8 uart, uint8 TxChar) WRITE_PERI_REG(UART_FIFO(uart) , TxChar); return OK; } - +#if 0 /****************************************************************************** * FunctionName : uart1_write_char * Description : Internal used function @@ -189,7 +189,7 @@ uart1_write_char(char c) uart_tx_one_char(UART1, c); } } - +#endif /****************************************************************************** * FunctionName : uart0_tx_buffer * Description : use uart0 to transfer buffer @@ -300,13 +300,15 @@ uart0_rx_intr_handler(void *para) } if (got_input && sig) { + // Only post a new handler request once the handler has fired clearing the last post if (isr_flag == *sig_flag) { isr_flag ^= 0x01; - task_post_low (sig, 0x8000 | isr_flag << 14 | false); + platform_post_high(sig, isr_flag); } } } +#ifdef BIT_RATE_AUTOBAUD static void uart_autobaud_timeout(void *timer_arg) { @@ -324,7 +326,6 @@ uart_autobaud_timeout(void *timer_arg) } } #include "pm/swtimer.h" - static void uart_init_autobaud(uint32_t uart_no) { @@ -339,22 +340,17 @@ uart_stop_autobaud() { os_timer_disarm(&autobaud_timer); } - +#endif /****************************************************************************** * FunctionName : uart_init * Description : user interface for init uart * Parameters : UartBautRate uart0_br - uart0 bautrate * UartBautRate uart1_br - uart1 bautrate - * os_signal_t sig_input - signal to post - * uint8 *flag_input - flag of consumer task * Returns : NONE *******************************************************************************/ void ICACHE_FLASH_ATTR -uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input, uint8 *flag_input) +uart_init(UartBautRate uart0_br, UartBautRate uart1_br) { - sig = sig_input; - sig_flag = flag_input; - // rom use 74880 baut_rate, here reinitialize UartDev.baut_rate = uart0_br; uart_config(UART0); @@ -378,6 +374,19 @@ uart_setup(uint8 uart_no) ETS_UART_INTR_ENABLE(); } +/****************************************************************************** + * FunctionName : uart_init_task + * Description : user interface for init uart task callback + * Parameters : os_signal_t sig_input - signal to post + * uint8 *flag_input - flag of consumer task + * Returns : NONE +*******************************************************************************/ + +void ICACHE_FLASH_ATTR uart_init_task(os_signal_t sig_input, uint8 *flag_input) { + sig = sig_input; + sig_flag = flag_input; +} + void ICACHE_FLASH_ATTR uart_set_alt_output_uart0(void (*fn)(char)) { alt_uart0_tx = fn; } diff --git a/app/include/driver/input.h b/app/include/driver/input.h new file mode 100644 index 0000000000..f18f48b6af --- /dev/null +++ b/app/include/driver/input.h @@ -0,0 +1,11 @@ +#ifndef READLINE_APP_H +#define READLINE_APP_H +typedef void (*uart_cb_t)(const char *buf, size_t len); + +extern void input_setup(int bufsize, const char *prompt); +extern void input_setup_receive(uart_cb_t uart_on_data_cb, int data_len, char end_char, bool run_input); +extern void input_setecho (bool flag); +extern void input_setprompt (const char *prompt); +extern void input_process_arm(void); + +#endif /* READLINE_APP_H */ diff --git a/app/include/driver/readline.h b/app/include/driver/readline.h deleted file mode 100644 index ae92cfd170..0000000000 --- a/app/include/driver/readline.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef READLINE_APP_H -#define READLINE_APP_H - -bool uart_getc(char *c); - -#endif /* READLINE_APP_H */ diff --git a/app/include/driver/uart.h b/app/include/driver/uart.h index 310f605417..eb9c4aad98 100644 --- a/app/include/driver/uart.h +++ b/app/include/driver/uart.h @@ -110,7 +110,8 @@ typedef struct { UartStopBitsNum stop_bits; } UartConfig; -void uart_init(UartBautRate uart0_br, UartBautRate uart1_br, os_signal_t sig_input, uint8 *flag_input); +void uart_init(UartBautRate uart0_br, UartBautRate uart1_br); +void uart_init_task(os_signal_t sig_input, uint8 *flag_input); UartConfig uart_get_config(uint8 uart_no); void uart0_alt(uint8 on); void uart0_sendStr(const char *str); diff --git a/app/include/task/task.h b/app/include/task/task.h index b090e54f28..5292d1dc35 100644 --- a/app/include/task/task.h +++ b/app/include/task/task.h @@ -1,35 +1,24 @@ #ifndef _TASK_H_ #define _TASK_H_ - -#include "ets_sys.h" -#include "osapi.h" -#include "os_type.h" -#include "user_interface.h" - -/* use LOW / MEDIUM / HIGH since it isn't clear from the docs which is higher */ - -#define TASK_PRIORITY_LOW 0 -#define TASK_PRIORITY_MEDIUM 1 -#define TASK_PRIORITY_HIGH 2 -#define TASK_PRIORITY_COUNT 3 - /* -* Signals are a 32-bit number of the form header:14; count:16, priority:2. The header -* is just a fixed fingerprint and the count is allocated serially by the task get_id() -* function. -*/ -#define task_post(priority,handle,param) system_os_post(priority, ((handle) | priority), param) -#define task_post_low(handle,param) task_post(TASK_PRIORITY_LOW, handle, param) -#define task_post_medium(handle,param) task_post(TASK_PRIORITY_MEDIUM, handle, param) -#define task_post_high(handle,param) task_post(TASK_PRIORITY_HIGH, handle, param) - -#define task_handle_t os_signal_t -#define task_param_t os_param_t - -typedef void (*task_callback_t)(task_param_t param, uint8 prio); - -bool task_init_handler(uint8 priority, uint8 qlen); -task_handle_t task_get_id(task_callback_t t); +** The task interface is now part of the core platform interface. +** This header is preserved for backwards compatability only. +*/ +#include "platform.h" + +#define TASK_PRIORITY_LOW PLATFORM_TASK_PRIORITY_LOW +#define TASK_PRIORITY_MEDIUM PLATFORM_TASK_PRIORITY_MEDIUM +#define TASK_PRIORITY_HIGH PLATFORM_TASK_PRIORITY_HIGH + +#define task_post(priority,handle,param) platform_post(priority,handle,param) +#define task_post_low(handle,param) platform_post_low(handle,param) +#define task_post_medium(handle,param) platform_post_medium(handle,param) +#define task_post_high(handle,param) platform_post_high(handle,param) + +#define task_handle_t platform_task_handle_t +#define task_param_t platform_task_param_t +#define task_callback_t platform_task_callback_t +#define task_get_id platform_task_get_id #endif diff --git a/app/include/user_config.h b/app/include/user_config.h index 4866e6a91d..d0b923b51c 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -263,14 +263,14 @@ extern void dbg_printf(const char *fmt, ...); #ifdef NODE_DEBUG #define NODE_DBG dbg_printf #else -#define NODE_DBG +#define NODE_DBG( ... ) #endif /* NODE_DEBUG */ #define NODE_ERROR #ifdef NODE_ERROR #define NODE_ERR dbg_printf #else -#define NODE_ERR +#define NODE_ERR( ... ) #endif /* NODE_ERROR */ // #define GPIO_SAFE_NO_INTR_ENABLE diff --git a/app/lua/Makefile b/app/lua/Makefile index 16d97ab05d..aeb66d1755 100644 --- a/app/lua/Makefile +++ b/app/lua/Makefile @@ -16,7 +16,7 @@ SUBDIRS = luac_cross GEN_LIBS = liblua.a endif -STD_CFLAGS=-std=gnu11 -Wimplicit +STD_CFLAGS=-std=gnu11 -Wimplicit -Wall ############################################################# # Configuration i.e. compile options etc. diff --git a/app/lua/lauxlib.c b/app/lua/lauxlib.c index 773d01a35f..07835a19e7 100644 --- a/app/lua/lauxlib.c +++ b/app/lua/lauxlib.c @@ -824,10 +824,9 @@ static int errfsfile (lua_State *L, const char *what, int fnameindex) { } -LUALIB_API int luaL_loadfsfile (lua_State *L, const char *filename) { +LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) { LoadFSF lf; - int status, readstatus; - int c; + int status, c; int fnameindex = lua_gettop(L) + 1; /* index of filename on the stack */ lf.extraline = 0; if (filename == NULL) { diff --git a/app/lua/lauxlib.h b/app/lua/lauxlib.h index 09dbd2c097..b769ad2ffb 100644 --- a/app/lua/lauxlib.h +++ b/app/lua/lauxlib.h @@ -79,7 +79,7 @@ LUALIB_API void (luaL_unref) (lua_State *L, int t, int ref); #ifdef LUA_CROSS_COMPILER LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); #else -LUALIB_API int (luaL_loadfsfile) (lua_State *L, const char *filename); +LUALIB_API int (luaL_loadfile) (lua_State *L, const char *filename); #endif LUALIB_API int (luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, const char *name); @@ -119,7 +119,7 @@ LUALIB_API void luaL_assertfail(const char *file, int line, const char *message) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) #else #define luaL_dofile(L, fn) \ - (luaL_loadfsfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) + (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0)) #endif #define luaL_dostring(L, s) \ diff --git a/app/lua/lbaselib.c b/app/lua/lbaselib.c index 02b69bc872..00e6959f66 100644 --- a/app/lua/lbaselib.c +++ b/app/lua/lbaselib.c @@ -19,8 +19,6 @@ #include "lrotable.h" - - /* ** If your system does not support `stdout', you can just remove this function. ** If you need, you can define your own `print' function, following this @@ -40,20 +38,11 @@ static int luaB_print (lua_State *L) { if (s == NULL) return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("print")); -#if defined(LUA_USE_STDIO) - if (i>1) fputs("\t", c_stdout); - fputs(s, c_stdout); -#else - if (i>1) luai_writestring("\t", 1); - luai_writestring(s, strlen(s)); -#endif + if (i>1) puts("\t"); + puts(s); lua_pop(L, 1); /* pop result */ } -#if defined(LUA_USE_STDIO) - fputs("\n", c_stdout); -#else - luai_writeline(); -#endif + puts("\n"); return 0; } @@ -296,7 +285,7 @@ static int luaB_loadfile (lua_State *L) { #ifdef LUA_CROSS_COMPILER return load_aux(L, luaL_loadfile(L, fname)); #else - return load_aux(L, luaL_loadfsfile(L, fname)); + return load_aux(L, luaL_loadfile(L, fname)); #endif } @@ -343,7 +332,7 @@ static int luaB_dofile (lua_State *L) { #ifdef LUA_CROSS_COMPILER if (luaL_loadfile(L, fname) != 0) lua_error(L); #else - if (luaL_loadfsfile(L, fname) != 0) lua_error(L); + if (luaL_loadfile(L, fname) != 0) lua_error(L); #endif lua_call(L, 0, LUA_MULTRET); return lua_gettop(L) - n; diff --git a/app/lua/ldblib.c b/app/lua/ldblib.c index d3926e9d0f..f5193bdcbf 100644 --- a/app/lua/ldblib.c +++ b/app/lua/ldblib.c @@ -365,7 +365,7 @@ static int db_debug (lua_State *L) { #define LEVELS1 12 /* size of the first part of the stack */ #define LEVELS2 10 /* size of the second part of the stack */ -static int db_errorfb (lua_State *L) { +int debug_errorfb (lua_State *L) { int level; int firstpart = 1; /* still before eventual `...' */ int arg; @@ -436,7 +436,7 @@ LROT_PUBLIC_BEGIN(dblib) LROT_FUNCENTRY( setmetatable, db_setmetatable ) LROT_FUNCENTRY( setupvalue, db_setupvalue ) #endif - LROT_FUNCENTRY( traceback, db_errorfb ) + LROT_FUNCENTRY( traceback, debug_errorfb ) LROT_END(dblib, NULL, 0) LUALIB_API int luaopen_debug (lua_State *L) { diff --git a/app/lua/lflash.c b/app/lua/lflash.c index ba1c46d336..d2bf5cc2d5 100644 --- a/app/lua/lflash.c +++ b/app/lua/lflash.c @@ -70,7 +70,7 @@ struct OUTPUT { outBlock buffer; int ndx; uint32_t crc; - int (*fullBlkCB) (void); + void (*fullBlkCB) (void); int flashLen; int flagsLen; int flagsNdx; @@ -79,7 +79,6 @@ struct OUTPUT { } *out; #ifdef NODE_DEBUG -extern void dbg_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); void dumpStrt(stringtable *tb, const char *type) { int i,j; GCObject *o; @@ -173,15 +172,15 @@ LUAI_FUNC void luaN_init (lua_State *L) { } if ((fh->flash_sig & (~FLASH_SIG_ABSOLUTE)) != FLASH_SIG ) { - NODE_ERR("Flash sig not correct: %p vs %p\n", + NODE_ERR("Flash sig not correct: 0x%08x vs 0x%08x\n", fh->flash_sig & (~FLASH_SIG_ABSOLUTE), FLASH_SIG); return; } if (fh->pROhash == ALL_SET || ((fh->mainProto - cast(FlashAddr, fh)) >= fh->flash_size)) { - NODE_ERR("Flash size check failed: %p vs 0xFFFFFFFF; %p >= %p\n", - fh->mainProto - cast(FlashAddr, fh), fh->flash_size); + NODE_ERR("Flash size check failed: 0x%08x vs 0xFFFFFFFF; 0x%08x >= 0x%08x\n", + fh->pROhash, fh->mainProto - cast(FlashAddr, fh), fh->flash_size); return; } @@ -194,7 +193,7 @@ LUAI_FUNC void luaN_init (lua_State *L) { //extern void software_reset(void); static int loadLFS (lua_State *L); static int loadLFSgc (lua_State *L); -static int procFirstPass (void); +static void procFirstPass (void); /* * Library function called by node.flashreload(filename). @@ -270,7 +269,6 @@ LUALIB_API int luaN_reload_reboot (lua_State *L) { * - An array of the module names in the LFS */ LUAI_FUNC int luaN_index (lua_State *L) { - int i; int n = lua_gettop(L); /* Return nil + the LFS base address if the LFS size > 0 and it isn't loaded */ @@ -406,11 +404,10 @@ static uint8_t recall_byte (unsigned offset) { * - Once the flags array is in-buffer this is also captured. * This logic is slightly complicated by the last buffer is typically short. */ -int procFirstPass (void) { +void procFirstPass (void) { int len = (out->ndx % WRITE_BLOCKSIZE) ? out->ndx % WRITE_BLOCKSIZE : WRITE_BLOCKSIZE; if (out->ndx <= WRITE_BLOCKSIZE) { - uint32_t fl; /* Process the flash header and cache the FlashHeader fields we need */ FlashHeader *fh = cast(FlashHeader *, out->block[0]); out->flashLen = fh->flash_size; /* in bytes */ @@ -442,12 +439,10 @@ int procFirstPass (void) { memcpy(out->flags + out->flagsNdx, out->block[0]->byte + start, len - start); out->flagsNdx += (len -start) / WORDSIZE; /* flashLen and len are word aligned */ } - - return 1; } -int procSecondPass (void) { +void procSecondPass (void) { /* * The length rules are different for the second pass since this only processes * upto the flashLen and not the full image. This also works in word units. @@ -456,7 +451,7 @@ int procSecondPass (void) { int i, len = (out->ndx > out->flashLen) ? (out->flashLen % WRITE_BLOCKSIZE) / WORDSIZE : WRITE_BLOCKSIZE / WORDSIZE; - uint32_t *buf = (uint32_t *) out->buffer.byte, flags; + uint32_t *buf = (uint32_t *) out->buffer.byte, flags = 0; /* * Relocate all the addresses tagged in out->flags. This can't be done in * place because the out->blocks are still in use as dictionary content so @@ -492,7 +487,7 @@ int procSecondPass (void) { */ static int loadLFS (lua_State *L) { const char *fn = cast(const char *, lua_touserdata(L, 1)); - int i, n, res; + int i, res; uint32_t crc; /* Allocate and zero in and out structures */ @@ -541,12 +536,11 @@ static int loadLFS (lua_State *L) { flashErase(0,(out->flashLen - 1)/FLASH_PAGE_SIZE); flashSetPosition(0); - if (uzlib_inflate(get_byte, put_byte, recall_byte, - in->len, &crc, &in->inflate_state) != UZLIB_OK) - if (res < 0) { + if ((res = uzlib_inflate(get_byte, put_byte, recall_byte, + in->len, &crc, &in->inflate_state)) != UZLIB_OK) { const char *err[] = {"Data_error during decompression", "Chksum_error during decompression", - "Dictionary error during decompression" + "Dictionary error during decompression", "Memory_error during decompression"}; flash_error(err[UZLIB_DATA_ERROR - res]); } diff --git a/app/lua/lnodemcu.c b/app/lua/lnodemcu.c new file mode 100644 index 0000000000..6d8cf1a236 --- /dev/null +++ b/app/lua/lnodemcu.c @@ -0,0 +1,116 @@ +/* +** $Id: ltm.c,v 2.8.1.1 2007/12/27 13:02:25 roberto Exp $ +** Tag methods +** See Copyright Notice in lua.h +*/ + + +#define lnodemcu_c +#define LUA_CORE + +#include "lua.h" +#include + +#include "lobject.h" +#include "lstate.h" +#include "lauxlib.h" +#include "lgc.h" +#include "lstring.h" +#include "ltable.h" +#include "ltm.h" +#include "lrotable.h" +#include "platform.h" + +extern int debug_errorfb (lua_State *L); +#if 0 +extern int pipe_create(lua_State *L); +extern int pipe_read(lua_State *L); +extern int pipe_unread(lua_State *L); +extern int pipe_write(lua_State *L); +#endif +/* +** Error Reporting Task. We can't pass a string parameter to the error reporter +** directly through the task interface the call is wrapped in a C closure with +** the error string as an Upval and this is posted to call the Lua reporter. +*/ +static int report_traceback (lua_State *L) { +// **Temp** lua_rawgeti(L, LUA_REGISTRYINDEX, G(L)->error_reporter); + lua_getglobal(L, "print"); + lua_pushvalue(L, lua_upvalueindex(1)); + lua_call(L, 1, 0); /* Using an error handler would cause an infinite loop! */ + return 0; +} + +/* +** Catch all error handler for CB calls. This uses debug.traceback() to +** generate a full Lua traceback. +*/ +int luaN_traceback (lua_State *L) { + if (lua_isstring(L, 1)) { + lua_pushlightfunction(L, &debug_errorfb); + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback and return it as a string */ + lua_pushcclosure(L, report_traceback, 1); /* report with str as upval */ + luaN_posttask(L, LUA_TASK_HIGH); + } + return 0; +} + +/* +** Use in CBs and other C functions to call a Lua function. This includes +** an error handler which will catch any error and then post this to the +** registered reporter function as a separate follow-on task. +*/ +int luaN_call (lua_State *L, int narg, int res, int doGC) { // [-narg, +0, v] + int status; + int base = lua_gettop(L) - narg; + lua_pushcfunction(L, luaN_traceback); + lua_insert(L, base); /* put under args */ + status = lua_pcall(L, narg, (res < 0 ? LUA_MULTRET : res), base); + lua_remove(L, base); /* remove traceback function */ + /* force a complete garbage collection if requested */ + if (doGC) + lua_gc(L, LUA_GCCOLLECT, 0); + return status; +} + +static platform_task_handle_t task_handle = 0; + +/* +** Task callback handler. Uses luaN_call to do a protected call with full traceback +*/ +static void do_task (platform_task_param_t task_fn_ref, uint8_t prio) { + lua_State* L = lua_getstate(); + if (prio < 0|| prio > 2) + luaL_error(L, "invalid posk task"); + +/* Pop the CB func from the Reg */ +//dbg_printf("calling Reg[%u]\n", task_fn_ref); + lua_rawgeti(L, LUA_REGISTRYINDEX, (int) task_fn_ref); + luaL_checkanyfunction(L, -1); + luaL_unref(L, LUA_REGISTRYINDEX, (int) task_fn_ref); + lua_pushinteger(L, prio); + luaN_call (L, 1, 0, 1); +} + +/* +** Schedule a Lua function for task execution +*/ +#include "lstate.h" /*DEBUG*/ +LUA_API int luaN_posttask( lua_State* L, int prio ) { // [-1, +0, -] + if (!task_handle) + task_handle = platform_task_get_id(do_task); + + if (!lua_isanyfunction(L, -1) || prio < LUA_TASK_LOW|| prio > LUA_TASK_HIGH) + luaL_error(L, "invalid posk task"); +//void *cl = clvalue(L->top-1); + int task_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); +//dbg_printf("posting Reg[%u]=%p\n",task_fn_ref,cl); + if(!platform_post(prio, task_handle, (platform_task_param_t)task_fn_ref)) { + luaL_unref(L, LUA_REGISTRYINDEX, task_fn_ref); + luaL_error(L, "Task queue overflow. Task not posted"); + } + return task_fn_ref; +} + diff --git a/app/lua/loadlib.c b/app/lua/loadlib.c index a4ab42c97a..5005969b0d 100644 --- a/app/lua/loadlib.c +++ b/app/lua/loadlib.c @@ -397,7 +397,7 @@ static int loader_Lua (lua_State *L) { #ifdef LUA_CROSS_COMPILER if (luaL_loadfile(L, filename) != 0) #else - if (luaL_loadfsfile(L, filename) != 0) + if (luaL_loadfile(L, filename) != 0) #endif loaderror(L, filename); return 1; /* library loaded successfully */ diff --git a/app/lua/lobject.h b/app/lua/lobject.h index 7741ddbf2d..62bf224f70 100644 --- a/app/lua/lobject.h +++ b/app/lua/lobject.h @@ -113,7 +113,7 @@ typedef struct lua_TValue { #define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA) #define ttisrotable(o) (ttype(o) == LUA_TROTABLE) #define ttislightfunction(o) (ttype(o) == LUA_TLIGHTFUNCTION) - +#define ttisanyfunction(o) (ttisfunction(o) || ttislightfunction(o)) /* Macros to access values */ diff --git a/app/lua/lstate.c b/app/lua/lstate.c index 55e107a666..13600f8374 100644 --- a/app/lua/lstate.c +++ b/app/lua/lstate.c @@ -197,11 +197,12 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { g->memlimit = 0; #endif #ifndef LUA_CROSS_COMPILER - g->ROstrt.size = 0; - g->ROstrt.nuse = 0; - g->ROstrt.hash = NULL; - g->ROpvmain = NULL; - g->LFSsize = 0; + g->ROstrt.size = 0; + g->ROstrt.nuse = 0; + g->ROstrt.hash = NULL; + g->ROpvmain = NULL; + g->LFSsize = 0; + g->error_reporter = 0; #endif for (i=0; imt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) { diff --git a/app/lua/lstate.h b/app/lua/lstate.h index 88b7d57f5a..bd9b3660a8 100644 --- a/app/lua/lstate.h +++ b/app/lua/lstate.h @@ -98,6 +98,7 @@ typedef struct global_State { stringtable ROstrt; /* Flash-based hash table for RO strings */ Proto *ROpvmain; /* Flash-based Proto main */ int LFSsize; /* Size of Lua Flash Store */ + int error_reporter; /* Registry Index of error reporter task */ #endif } global_State; diff --git a/app/lua/lua.c b/app/lua/lua.c index 81b04751c2..80afc690b2 100644 --- a/app/lua/lua.c +++ b/app/lua/lua.c @@ -1,18 +1,16 @@ /* -** $Id: lua.c,v 1.160.1.2 2007/12/28 15:32:23 roberto Exp $ -** Lua stand-alone interpreter +** NodeMCU Lua 5.1 main initiator and comand interpreter ** See Copyright Notice in lua.h +** +** Note this is largely a backport of some new Lua 5.3 version but +** with API changes for Lua 5.1 compatability */ - #include #include #include -#include "user_interface.h" #include "user_version.h" -#include "driver/readline.h" -#include "driver/uart.h" -#include "platform.h" +#include "driver/input.h" #define lua_c @@ -21,498 +19,283 @@ #include "lauxlib.h" #include "lualib.h" #include "legc.h" -#include "lflash.h" #include "os_type.h" +extern int debug_errorfb (lua_State *L); +extern int pipe_create(lua_State *L); +extern int pipe_read(lua_State *L); +extern int pipe_unread(lua_State *L); + +#ifndef LUA_INIT_STRING +#define LUA_INIT_STRING "@init.lua" +#endif + +static int MLref = LUA_NOREF; lua_State *globalL = NULL; -static lua_Load gLoad; -static const char *progname = LUA_PROGNAME; +static int pmain (lua_State *L); +static int dojob (lua_State *L); -static void l_message (const char *pname, const char *msg) { -#if defined(LUA_USE_STDIO) - if (pname) fprintf(c_stderr, "%s: ", pname); - fprintf(c_stderr, "%s\n", msg); - fflush(c_stderr); -#else - if (pname) luai_writestringerror("%s: ", pname); +static void l_message (const char *msg) { luai_writestringerror("%s\n", msg); -#endif } - static int report (lua_State *L, int status) { if (status && !lua_isnil(L, -1)) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error object is not a string)"; - l_message(progname, msg); + l_message(msg); lua_pop(L, 1); } return status; } +static void l_print(lua_State *L, int n) { + lua_getglobal(L, "print"); + lua_insert(L, -n-1); + if (lua_pcall(L, n, 0, 0) != 0) + l_message(lua_pushfstring(L, "error calling " LUA_QL("print") " (%s)", + lua_tostring(L, -1))); +} static int traceback (lua_State *L) { - if (!lua_isstring(L, 1)) /* 'message' not a string? */ - return 1; /* keep it intact */ - lua_getfield(L, LUA_GLOBALSINDEX, "debug"); - if (!lua_istable(L, -1) && !lua_isrotable(L, -1)) { - lua_pop(L, 1); - return 1; - } - lua_getfield(L, -1, "traceback"); - if (!lua_isfunction(L, -1) && !lua_islightfunction(L, -1)) { - lua_pop(L, 2); - return 1; + if (lua_isstring(L, 1)) { + lua_pushlightfunction(L, &debug_errorfb); + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ } - lua_pushvalue(L, 1); /* pass error message */ - lua_pushinteger(L, 2); /* skip this function and traceback */ - lua_call(L, 2, 1); /* call debug.traceback */ + lua_settop(L, 1); return 1; } - static int docall (lua_State *L, int narg, int clear) { int status; - int base = lua_gettop(L) - narg; /* function index */ - lua_pushcfunction(L, traceback); /* push traceback function */ - lua_insert(L, base); /* put it under chunk and args */ - // signal(SIGINT, laction); + int base = lua_gettop(L) - narg; /* function index */ + lua_pushlightfunction(L, &traceback); /* push traceback function */ + lua_insert(L, base); /* put it under chunk and args */ status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); - // signal(SIGINT, SIG_DFL); - lua_remove(L, base); /* remove traceback function */ + lua_remove(L, base); /* remove traceback function */ /* force a complete garbage collection in case of errors */ if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); return status; } - static void print_version (lua_State *L) { - lua_pushliteral (L, "\n" NODE_VERSION " build " BUILD_DATE " powered by " LUA_RELEASE " on SDK "); + lua_pushliteral (L, "\n" NODE_VERSION " build " BUILD_DATE + " powered by " LUA_RELEASE " on SDK "); lua_pushstring (L, SDK_VERSION); lua_concat (L, 2); const char *msg = lua_tostring (L, -1); - l_message (NULL, msg); + l_message (msg); lua_pop (L, 1); } - -static int getargs (lua_State *L, char **argv, int n) { - int narg; - int i; - int argc = 0; - while (argv[argc]) argc++; /* count total number of arguments */ - narg = argc - (n + 1); /* number of arguments to the script */ - luaL_checkstack(L, narg + 3, "too many arguments to script"); - for (i=n+1; i < argc; i++) - lua_pushstring(L, argv[i]); - lua_createtable(L, narg, n + 1); - for (i=0; i < argc; i++) { - lua_pushstring(L, argv[i]); - lua_rawseti(L, -2, i - n); - } - return narg; -} - -static int dofsfile (lua_State *L, const char *name) { - int status = luaL_loadfsfile(L, name) || docall(L, 0, 1); +static int dofile (lua_State *L, const char *name) { + int status = luaL_loadfile(L, name) || docall(L, 0, 1); return report(L, status); } - static int dostring (lua_State *L, const char *s, const char *name) { int status = luaL_loadbuffer(L, s, strlen(s), name) || docall(L, 0, 1); return report(L, status); } - -static int dolibrary (lua_State *L, const char *name) { - lua_getglobal(L, "require"); - lua_pushstring(L, name); - return report(L, docall(L, 1, 1)); -} - static const char *get_prompt (lua_State *L, int firstline) { const char *p; - lua_getfield(L, LUA_GLOBALSINDEX, firstline ? "_PROMPT" : "_PROMPT2"); + lua_getglobal(L, firstline ? "_PROMPT" : "_PROMPT2"); p = lua_tostring(L, -1); if (p == NULL) p = (firstline ? LUA_PROMPT : LUA_PROMPT2); lua_pop(L, 1); /* remove global */ return p; } - +#define EOFMARK LUA_QL("") static int incomplete (lua_State *L, int status) { if (status == LUA_ERRSYNTAX) { size_t lmsg; const char *msg = lua_tolstring(L, -1, &lmsg); - const char *tp = msg + lmsg - (sizeof(LUA_QL("")) - 1); - if (strstr(msg, LUA_QL("")) == tp) { + if (!strcmp(msg+lmsg-sizeof(EOFMARK)+1, EOFMARK)) { lua_pop(L, 1); return 1; } } - return 0; /* else... */ + return 0; } +/* +** dojob is the CB reader for the input pipe and follows the calling convention +** for pipe reader CBs. It has one argument: the stdin pipe that it is reading. +*/ +static int dojob (lua_State *L) { + size_t l; + int status; + const char *prompt; + +//dbg_printf("dojob entered\n"); + lua_settop(L, 1); /* pipe obj at S[1] */ + lua_pushlightfunction(L, &pipe_read); /* pobj:read at S[2] */ + lua_pushvalue(L, 1); /* dup pobj to S[3] */ + lua_pushliteral(L, "\n+"); /* S[4] = "\n+" */ + lua_call(L, 2, 1); /* S[2] = pobj:read("\n+") */ + const char* b = lua_tolstring(L, 2, &l); /* b = NULL if S[2] is nil */ + + if ((lua_isnil(L, 2) || l == 0)) { + /* If the pipe is empty then return false to suppress automatic reposting */ +//dbg_printf("stdin empty\n"); + lua_pushboolean(L, false); + return 1; /* return false */ + } -/* check that argument has no extra characters at the end */ -#define notail(x) {if ((x)[2] != '\0') return -1;} - - -static int collectargs (char **argv, int *pi, int *pv, int *pe) { - int i; - for (i = 1; argv[i] != NULL; i++) { - if (argv[i][0] != '-') /* not an option? */ - return i; - switch (argv[i][1]) { /* option */ - case '-': - notail(argv[i]); - return (argv[i+1] != NULL ? i+1 : 0); - case '\0': - return i; - case 'i': - notail(argv[i]); - *pi = 1; /* go through */ - case 'v': - notail(argv[i]); - *pv = 1; - break; - case 'e': - *pe = 1; /* go through */ - case 'm': /* go through */ - case 'l': - if (argv[i][2] == '\0') { - i++; - if (argv[i] == NULL) return -1; - } - break; - default: return -1; /* invalid option */ - } + if (b[l-1] != '\n') { +//dbg_printf("unreading part line\n"); + /* likewise if not CR terminated, then unread and ditto */ + lua_pushlightfunction(L, &pipe_unread); /* pobj:read at S[1] */ + lua_insert(L, 1); + lua_call(L, 2, 0); /* pobj:unread(line) */ + lua_pushboolean(L, false); + return 1; /* return false */ + } else { +//dbg_printf("popping CR terminated string(%d) %s", l-1, b); } - return 0; -} + /* + * Now we can process a proper CR terminated line + */ + lua_pushlstring(L, b, --l); /* remove end CR */ + lua_remove(L, 2); + b = lua_tostring(L, 2); + + if (MLref != LUA_NOREF) { + /* processing multiline */ + lua_rawgeti(L, LUA_REGISTRYINDEX, MLref); /* insert prev lines(s) */ + lua_pushliteral(L, "\n"); /* insert CR */ + lua_pushvalue(L, 2); /* dup new line */ + lua_concat(L, 3); /* concat all 3 */ + lua_remove(L, 2); /* and shift down to S[2] */ + } else if (b[0] == '=') { + /* If firstline and of the format = */ + lua_pushfstring(L, "return %s", b+1); + lua_remove(L, 2); + } + + /* ToS is at S[2] which contains the putative chunk to be compiled */ + status = luaL_loadbuffer(L, lua_tostring(L, 2), lua_strlen(L, 2), "=stdin"); -static int runargs (lua_State *L, char **argv, int n) { - int i; - for (i = 1; i < n; i++) { - if (argv[i] == NULL) continue; - lua_assert(argv[i][0] == '-'); - switch (argv[i][1]) { /* option */ - case 'e': { - const char *chunk = argv[i] + 2; - if (*chunk == '\0') chunk = argv[++i]; - lua_assert(chunk != NULL); - if (dostring(L, chunk, "=(command line)") != 0) - return 1; - break; - } - case 'm': { - const char *limit = argv[i] + 2; - int memlimit=0; - if (*limit == '\0') limit = argv[++i]; - lua_assert(limit != NULL); - memlimit = atoi(limit); - lua_gc(L, LUA_GCSETMEMLIMIT, memlimit); - break; - } - case 'l': { - const char *filename = argv[i] + 2; - if (*filename == '\0') filename = argv[++i]; - lua_assert(filename != NULL); - if (dolibrary(L, filename)) - return 1; /* stop if file fails */ - break; - } - default: break; + if (incomplete(L, status)) { + /* Store line back in the Reg mlref sot */ + if (MLref == LUA_NOREF) { + MLref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + lua_rawseti(L, LUA_REGISTRYINDEX, MLref); } + } else { + /* compile finished OK or with hard error */ + lua_remove(L, 2); /* remove line because now redundant */ + if (MLref!= LUA_NOREF) { /* also remove multiline if it exists */ + luaL_unref(L, LUA_REGISTRYINDEX, MLref); + MLref= LUA_NOREF; + } + /* Execute the compiled chunk of successful */ + if (status == 0) { + status = docall(L, 0, 0); + } + /* print any returned results or error message */ + if (status && !lua_isnil(L, -1)) + l_print(L, 1); + if (status == 0 && lua_gettop(L) - 1) + l_print(L, lua_gettop(L) - 1); + + lua_settop(L, 2); + if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); } - return 0; -} - -#ifndef LUA_INIT_STRING -#define LUA_INIT_STRING "@init.lua" -#endif + prompt = get_prompt(L, MLref!= LUA_NOREF ? 0 : 1); + input_setprompt(prompt); + puts(prompt); -static int handle_luainit (lua_State *L) { - const char *init = LUA_INIT_STRING; - if (init[0] == '@') - return dofsfile(L, init+1); - else - return dostring(L, init, LUA_INIT); + lua_pushnil(L); + return 1; /* return nil will retask if pipe not empty */ } -struct Smain { - int argc; - char **argv; - int status; -}; - +/* + * Kick off library and UART input handling before opening the module + * libraries. Note that as this is all done within a Lua task, so error + * handling is left to the Lua task traceback mechanism. + */ +extern void luaL_dbgbreak(void); static int pmain (lua_State *L) { - struct Smain *s = (struct Smain *)lua_touserdata(L, 1); - char **argv = s->argv; - int script; - int has_i = 0, has_v = 0, has_e = 0; + const char *init = LUA_INIT_STRING; globalL = L; - if (argv[0] && argv[0][0]) progname = argv[0]; - lua_gc(L, LUA_GCSTOP, 0); /* stop collector during initialization */ - luaL_openlibs(L); /* open libraries */ - lua_gc(L, LUA_GCRESTART, 0); - print_version(L); - s->status = handle_luainit(L); - script = collectargs(argv, &has_i, &has_v, &has_e); - if (script < 0) { /* invalid args? */ - s->status = 1; - return 0; - } - s->status = runargs(L, argv, (script > 0) ? script : s->argc); - if (s->status != 0) return 0; - return 0; -} - -static void dojob(lua_Load *load); -static bool readline(lua_Load *load); -#ifdef LUA_RPC -int main (int argc, char **argv) { -#else -int lua_main (int argc, char **argv) { -#endif - int status; - struct Smain s; - -#if defined(NODE_DEBUG) && defined(DEVELOPMENT_USE_GDB) && \ - defined(DEVELOPMENT_BREAK_ON_STARTUP_PIN) && DEVELOPMENT_BREAK_ON_STARTUP_PIN > 0 - platform_gpio_mode( DEVELOPMENT_BREAK_ON_STARTUP_PIN, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_PULLUP ); - lua_assert(platform_gpio_read(DEVELOPMENT_BREAK_ON_STARTUP_PIN)); // Break if pin pulled low -#endif - - lua_State *L = lua_open(); /* create state */ - if (L == NULL) { - l_message(argv[0], "cannot create state: not enough memory"); - return EXIT_FAILURE; - } - s.argc = argc; - s.argv = argv; - - status = lua_cpcall(L, &pmain, &s); - - report(L, status); - - gLoad.L = L; - gLoad.firstline = 1; - gLoad.done = 0; - gLoad.line = malloc(LUA_MAXINPUT); - gLoad.len = LUA_MAXINPUT; - gLoad.line_position = 0; - gLoad.prmt = get_prompt(L, 1); - - dojob(&gLoad); - - NODE_DBG("Heap size:%d.\n",system_get_free_heap_size()); + lua_gc(L, LUA_GCSTOP, 0); /* stop GC during initialization */ + luaL_openlibs(L); /* open libraries */ + lua_gc(L, LUA_GCRESTART, 0); /* restart GC and set EGC mode */ legc_set_mode( L, EGC_ALWAYS, 4096 ); - // legc_set_mode( L, EGC_ON_MEM_LIMIT, 4096 ); - // lua_close(L); - return (status || s.status) ? EXIT_FAILURE : EXIT_SUCCESS; -} - -int lua_put_line(const char *s, size_t l) { - if (s == NULL || ++l > LUA_MAXINPUT || gLoad.line_position > 0) - return 0; - memcpy(gLoad.line, s, l); - gLoad.line[l] = '\0'; - gLoad.line_position = l; - gLoad.done = 1; - NODE_DBG("Get command: %s\n", gLoad.line); - return 1; -} - -void lua_handle_input (bool force) -{ - while (gLoad.L && (force || readline (&gLoad))) { - NODE_DBG("Handle Input: first=%u, pos=%u, len=%u, actual=%u, line=%s\n", gLoad.firstline, - gLoad.line_position, gLoad.len, strlen(gLoad.line), gLoad.line); - dojob (&gLoad); - force = false; - } -} - -void donejob(lua_Load *load){ - lua_close(load->L); -} + lua_settop(L, 0); -static void dojob(lua_Load *load){ - size_t l, rs; - int status; - char *b = load->line; - lua_State *L = load->L; - - const char *oldprogname = progname; - progname = NULL; - - do{ - if(load->done == 1){ - l = strlen(b); - if (l > 0 && b[l-1] == '\n') /* line ends with newline? */ - b[l-1] = '\0'; /* remove it */ - if (load->firstline && b[0] == '=') /* first line starts with `=' ? */ - lua_pushfstring(L, "return %s", b+1); /* change it to `return' */ - else - lua_pushstring(L, b); - if(load->firstline != 1){ - lua_pushliteral(L, "\n"); /* add a new line... */ - lua_insert(L, -2); /* ...between the two lines */ - lua_concat(L, 3); /* join them */ - } - - status = luaL_loadbuffer(L, lua_tostring(L, 1), lua_strlen(L, 1), "=stdin"); - if (!incomplete(L, status)) { /* cannot try to add lines? */ - lua_remove(L, 1); /* remove line */ - if (status == 0) { - status = docall(L, 0, 0); - } - report(L, status); - if (status == 0 && lua_gettop(L) > 0) { /* any result to print? */ - lua_getglobal(L, "print"); - lua_insert(L, 1); - if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != 0) - l_message(progname, lua_pushfstring(L, - "error calling " LUA_QL("print") " (%s)", - lua_tostring(L, -1))); - } - load->firstline = 1; - load->prmt = get_prompt(L, 1); - lua_settop(L, 0); - /* force a complete garbage collection in case of errors */ - if (status != 0) lua_gc(L, LUA_GCCOLLECT, 0); - } else { - load->firstline = 0; - load->prmt = get_prompt(L, 0); - } - } - }while(0); + lua_pushliteral(L, "stdin"); + lua_pushlightfunction(L, &pipe_create); + lua_pushlightfunction(L, &dojob); + lua_pushinteger(L, LUA_TASK_LOW); + lua_call(L, 2, 1); /* ToS = pipe.create(dojob, low_priority) */ + lua_rawset(L, LUA_REGISTRYINDEX); /* and stash input pipe in Reg["stdin"] */ - progname = oldprogname; + input_setup(LUA_MAXINPUT, get_prompt(L, 1)); + lua_input_string(" \n", 2); /* queue CR to issue first prompt */ + print_version(L); - load->done = 0; - load->line_position = 0; - memset(load->line, 0, load->len); - puts(load->prmt); + /* and last of all, kick off application initialisation */ + if (init[0] == '@') + dofile(L, init+1); + else + dostring(L, init, LUA_INIT); + return 0; } -#ifndef uart_putc -#define uart_putc uart0_putc -#endif -extern bool uart_on_data_cb(const char *buf, size_t len); -extern bool uart0_echo; -extern bool run_input; -extern uint16_t need_len; -extern int16_t end_char; -static char last_nl_char = '\0'; -static bool readline(lua_Load *load){ - // NODE_DBG("readline() is called.\n"); - bool need_dojob = false; - char ch; - while (uart_getc(&ch)) - { - if(run_input) - { - char tmp_last_nl_char = last_nl_char; - // reset marker, will be finally set below when newline is processed - last_nl_char = '\0'; - - /* handle CR & LF characters - filters second char of LF&CR (\n\r) or CR&LF (\r\n) sequences */ - if ((ch == '\r' && tmp_last_nl_char == '\n') || // \n\r sequence -> skip \r - (ch == '\n' && tmp_last_nl_char == '\r')) // \r\n sequence -> skip \n - { - continue; - } - - /* backspace key */ - else if (ch == 0x7f || ch == 0x08) - { - if (load->line_position > 0) - { - if(uart0_echo) uart_putc(0x08); - if(uart0_echo) uart_putc(' '); - if(uart0_echo) uart_putc(0x08); - load->line_position--; - } - load->line[load->line_position] = 0; - continue; - } - /* EOT(ctrl+d) */ - // else if (ch == 0x04) - // { - // if (load->line_position == 0) - // // No input which makes lua interpreter close - // donejob(load); - // else - // continue; - // } - - /* end of line */ - if (ch == '\r' || ch == '\n') - { - last_nl_char = ch; - - load->line[load->line_position] = 0; - if(uart0_echo) uart_putc('\n'); - uart_on_data_cb(load->line, load->line_position); - if (load->line_position == 0) - { - /* Get a empty line, then go to get a new line */ - puts(load->prmt); - continue; - } else { - load->done = 1; - need_dojob = true; - break; - } - } - - /* other control character or not an acsii character */ - // if (ch < 0x20 || ch >= 0x80) - // { - // continue; - // } - - /* echo */ - if(uart0_echo) uart_putc(ch); - - /* it's a large line, discard it */ - if ( load->line_position + 1 >= load->len ){ - load->line_position = 0; - } - } - - load->line[load->line_position] = ch; - load->line_position++; - - if(!run_input) - { - if( ((need_len!=0) && (load->line_position >= need_len)) || \ - (load->line_position >= load->len) || \ - ((end_char>=0) && ((unsigned char)ch==(unsigned char)end_char)) ) - { - uart_on_data_cb(load->line, load->line_position); - load->line_position = 0; - } - } - - ch = 0; - } - - if( (load->line_position > 0) && (!run_input) && (need_len==0) && (end_char<0) ) - { - uart_on_data_cb(load->line, load->line_position); - load->line_position = 0; +/* +** The system initialisation CB nodemcu_init() calls lua_main() to startup +** the Lua environment by calling lua_open() which initiates the core Lua VM. +** The initialisation of the libraries, etc. is carried out by pmain in a +** separate Lua task, which also kicks off the user application through the +** LUA_INIT_STRING hook. +*/ +void lua_main (void) { + lua_State *L = lua_open(); /* create state */ + if (L == NULL) { + l_message("cannot create state: not enough memory"); + return; } + lua_pushlightfunction(L, &pmain); /* Call 'pmain' as a high priority task */ + luaN_posttask(L, LUA_TASK_HIGH); +} - return need_dojob; +/* +** The Lua interpreter is event-driven and task-oriented in NodeMCU rather than +** based on a readline poll loop as in the standard implementation. Input lines +** can come from one of two sources: the application can "push" lines for the +** interpreter to compile and execute, or they can come from the UART. To +** minimise application blocking, the lines are queued in a pipe when received, +** with the Lua interpreter task attached to the pipe as its reader task. This +** CB processes one line of input per task execution. +** +** Even though lines can be emitted from independent sources (the UART and the +** node API), and they could in theory get interleaved, the strategy here is +** "let the programmer beware": interactive input will normally only occur in +** development and injected input occur in telnet type applications. If there +** is a need for interlocks, then the application should handle this. +*/ +//static int n = 0; +void lua_input_string (const char *line, int len) { + lua_State *L = globalL; + lua_getfield(L, LUA_REGISTRYINDEX, "stdin"); + lua_rawgeti(L, -1, 1); /* get the pipe_write from stdin[1] */ + lua_insert(L, -2); /* stick above the pipe */ + lua_pushlstring(L, line, len); + +//const char*b = lua_tostring(L, -1); +//dbg_printf("Pushing (%u): %s", len, b); + lua_call(L, 2, 0); /* stdin:write(line) */ } diff --git a/app/lua/lua.h b/app/lua/lua.h index a4b5c4e219..874f0fc43c 100644 --- a/app/lua/lua.h +++ b/app/lua/lua.h @@ -273,9 +273,11 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); #define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION) #define lua_islightfunction(L,n) (lua_type(L, (n)) == LUA_TLIGHTFUNCTION) +#define lua_isanyfunction(L,n) (lua_isfunction(L,n) || lua_islightfunction(L,n)) #define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) #define lua_isrotable(L,n) (lua_type(L, (n)) == LUA_TROTABLE) -#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) +#define lua_isanytable(L,n) (lua_istable(L,n) || lua_isrotable(L,n)) +#define lua_islightuserdata(L,n) (lua_type(L, (n) == LUA_TLIGHTUSERDATA) #define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) @@ -375,20 +377,19 @@ struct lua_Debug { /* }====================================================================== */ -typedef struct __lua_load{ - lua_State *L; - int firstline; - char *line; - int line_position; - size_t len; - int done; - const char *prmt; -}lua_Load; - -int lua_main( int argc, char **argv ); #ifndef LUA_CROSS_COMPILER -void lua_handle_input (bool force); +#define LUA_QUEUE_APP 0 +#define LUA_QUEUE_UART 1 +#define LUA_TASK_LOW 0 +#define LUA_TASK_MEDIUM 1 +#define LUA_TASK_HIGH 2 + +void lua_main (void); +void lua_input_string (const char *line, int len); +int luaN_posttask (lua_State* L, int prio); +int luaN_call (lua_State *L, int narg, int res, int dogc); +/**DEBUG**/extern void dbg_printf(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); #endif /****************************************************************************** diff --git a/app/modules/gpio.c b/app/modules/gpio.c index 939d3d25f5..626ce18648 100644 --- a/app/modules/gpio.c +++ b/app/modules/gpio.c @@ -5,6 +5,7 @@ #include "lauxlib.h" #include "lmem.h" #include "platform.h" +#include "task/task.h" #include "user_interface.h" #include #include diff --git a/app/modules/gpio_pulse.c b/app/modules/gpio_pulse.c index 15909572f4..5032d4426e 100644 --- a/app/modules/gpio_pulse.c +++ b/app/modules/gpio_pulse.c @@ -45,7 +45,7 @@ typedef struct { static int active_pulser_ref; static pulse_t *active_pulser; -static task_handle_t tasknumber; +static platform_task_handle_t tasknumber; static int gpio_pulse_push_state(lua_State *L, pulse_t *pulser) { uint32_t now; @@ -321,7 +321,7 @@ static void ICACHE_RAM_ATTR gpio_pulse_timeout(os_param_t p) { active_pulser->steps++; } platform_hw_timer_close(TIMER_OWNER); - task_post_low(tasknumber, (task_param_t)0); + platform_post_low(tasknumber, 0); return; } active_pulser->steps++; @@ -341,7 +341,7 @@ static void ICACHE_RAM_ATTR gpio_pulse_timeout(os_param_t p) { int16_t stop = active_pulser->stop_pos; if (stop == -2 || stop == active_pulser->entry_pos) { platform_hw_timer_close(TIMER_OWNER); - task_post_low(tasknumber, (task_param_t)0); + platform_post_low(tasknumber, 0); return; } @@ -488,7 +488,7 @@ LROT_END( gpio_pulse, gpio_pulse, LROT_MASK_INDEX ) int gpio_pulse_init(lua_State *L) { luaL_rometatable(L, "gpio.pulse", LROT_TABLEREF(pulse)); - tasknumber = task_get_id(gpio_pulse_task); + tasknumber = platform_task_get_id(gpio_pulse_task); return 0; } diff --git a/app/modules/net.c b/app/modules/net.c index fdbd575d8d..51efae715b 100644 --- a/app/modules/net.c +++ b/app/modules/net.c @@ -736,6 +736,14 @@ int net_getaddr( lua_State *L ) { lua_pushstring(L, addr_str); return 2; } +#if 0 +static void dbg_print_ud(const char *title, lnet_userdata *ud) { + int i; + dbg_printf("%s: Userdata %p:", title, ud); + for (i=0; i<(sizeof(*ud)/sizeof(uint32_t)); i++) + dbg_printf( " 0x%08x", ((uint32_t *)ud)[i]); + dbg_printf("\n"); +#endif // Lua: client/server/socket:close() int net_close( lua_State *L ) { @@ -764,11 +772,14 @@ int net_close( lua_State *L ) { } if (ud->type == TYPE_TCP_SERVER || (ud->pcb == NULL && ud->client.wait_dns == 0)) { - lua_gc(L, LUA_GCSTOP, 0); +// lua_gc(L, LUA_GCSTOP, 0); luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); ud->self_ref = LUA_NOREF; - lua_gc(L, LUA_GCRESTART, 0); +// lua_gc(L, LUA_GCRESTART, 0); } +#if 0 + dbg_print_ud("close exit", ud); +#endif return 0; } @@ -813,10 +824,13 @@ int net_delete( lua_State *L ) { ud->server.cb_accept_ref = LUA_NOREF; break; } - lua_gc(L, LUA_GCSTOP, 0); +// lua_gc(L, LUA_GCSTOP, 0); luaL_unref(L, LUA_REGISTRYINDEX, ud->self_ref); ud->self_ref = LUA_NOREF; - lua_gc(L, LUA_GCRESTART, 0); +// lua_gc(L, LUA_GCRESTART, 0); +#if 0 + dbg_print_ud("delete end", ud); +#endif return 0; } diff --git a/app/modules/node.c b/app/modules/node.c index a44f3fa43c..d3f59a4bcd 100644 --- a/app/modules/node.c +++ b/app/modules/node.c @@ -171,69 +171,59 @@ static int node_heap( lua_State* L ) return 1; } -extern int lua_put_line(const char *s, size_t l); -extern bool user_process_input(bool force); - // Lua: input("string") static int node_input( lua_State* L ) { - size_t l = 0; - const char *s = luaL_checklstring(L, 1, &l); - if (lua_put_line(s, l)) { - NODE_DBG("Result (if any):\n"); - user_process_input(true); - } + luaL_checkstring(L, 1); + lua_getfield(L, LUA_REGISTRYINDEX, "stdin"); + lua_rawgeti(L, -1, 1); /* get the pipe_write func from stdin[1] */ + lua_insert(L, -2); /* and move above the pipe ref */ + lua_pushvalue(L, 1); + lua_call(L, 2, 0); /* stdin:write(line) */ return 0; } -static int output_redir_ref = LUA_NOREF; static int serial_debug = 1; + void output_redirect(const char *str) { lua_State *L = lua_getstate(); - // if(strlen(str)>=TX_BUFF_SIZE){ - // NODE_ERR("output too long.\n"); - // return; - // } - - if (output_redir_ref == LUA_NOREF) { - uart0_sendStr(str); - return; - } + 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 */ + if (serial_debug) { + uart0_sendStr(str); + } + lua_rawgeti(L, -1, 1); /* get the pipe_write func from stdout[1] */ + lua_insert(L, -2); /* and move above the pipe ref */ + lua_pushstring(L, str); + lua_call(L, 2, 0); /* Reg.stdout:write(str) */ - if (serial_debug != 0) { + } else { /* reg.stdout == nil */ uart0_sendStr(str); } - - lua_rawgeti(L, LUA_REGISTRYINDEX, output_redir_ref); - lua_pushstring(L, str); - lua_call(L, 1, 0); // this call back function should never user output. + lua_settop(L, n); /* Make sure all code paths leave stack unchanged */ } +extern int pipe_create(lua_State *L); + // Lua: output(function(c), debug) static int node_output( lua_State* L ) { - // luaL_checkanyfunction(L, 1); - if (lua_type(L, 1) == LUA_TFUNCTION || lua_type(L, 1) == LUA_TLIGHTFUNCTION) { - lua_pushvalue(L, 1); // copy argument (func) to the top of stack - if (output_redir_ref != LUA_NOREF) - luaL_unref(L, LUA_REGISTRYINDEX, output_redir_ref); - output_redir_ref = luaL_ref(L, LUA_REGISTRYINDEX); - } else { // unref the key press function - if (output_redir_ref != LUA_NOREF) - luaL_unref(L, LUA_REGISTRYINDEX, output_redir_ref); - output_redir_ref = LUA_NOREF; + serial_debug = (lua_isnumber(L, 2) && lua_tointeger(L, 2) == 0) ? 0 : 1; + lua_settop(L, 1); + if (lua_isanyfunction(L, 1)) { + lua_pushlightfunction(L, &pipe_create); + lua_insert(L, 1); + lua_pushinteger(L, LUA_TASK_MEDIUM); + lua_call(L, 2, 1); /* T[1] = pipe.create(dojob, low_priority) */ + } else { // remove the stdout pipe + lua_pop(L,1); + lua_pushnil(L); /* T[1] = nil */ serial_debug = 1; - return 0; - } - - 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 } - + lua_pushliteral(L, "stdout"); + lua_insert(L, 1); + lua_rawset(L, LUA_REGISTRYINDEX); /* Reg.stdout = nil or pipe */ return 0; } @@ -274,7 +264,7 @@ static int node_compile( lua_State* L ) output[strlen(output) - 1] = '\0'; NODE_DBG(output); NODE_DBG("\n"); - if (luaL_loadfsfile(L, fname) != 0) { + if (luaL_loadfile(L, fname) != 0) { luaM_free( L, output ); return luaL_error(L, lua_tostring(L, -1)); } @@ -315,39 +305,19 @@ static int node_compile( lua_State* L ) return 0; } -// Task callback handler for node.task.post() -static task_handle_t do_node_task_handle; -static void do_node_task (task_param_t task_fn_ref, uint8_t prio) -{ - lua_State* L = lua_getstate(); - lua_rawgeti(L, LUA_REGISTRYINDEX, (int)task_fn_ref); - luaL_unref(L, LUA_REGISTRYINDEX, (int)task_fn_ref); - lua_pushinteger(L, prio); - lua_call(L, 1, 0); -} - // Lua: node.task.post([priority],task_cb) -- schedule a task for execution next static int node_task_post( lua_State* L ) { - int n = 1, Ltype = lua_type(L, 1); + int n=1; unsigned priority = TASK_PRIORITY_MEDIUM; - if (Ltype == LUA_TNUMBER) { + if (lua_type(L, 1) == LUA_TNUMBER) { priority = (unsigned) luaL_checkint(L, 1); luaL_argcheck(L, priority <= TASK_PRIORITY_HIGH, 1, "invalid priority"); - Ltype = lua_type(L, ++n); - } - luaL_argcheck(L, Ltype == LUA_TFUNCTION || Ltype == LUA_TLIGHTFUNCTION, n, "invalid function"); - lua_pushvalue(L, n); - - int task_fn_ref = luaL_ref(L, LUA_REGISTRYINDEX); - - if (!do_node_task_handle) // bind the task handle to do_node_task on 1st call - do_node_task_handle = task_get_id(do_node_task); - - if(!task_post(priority, do_node_task_handle, (task_param_t)task_fn_ref)) { - luaL_unref(L, LUA_REGISTRYINDEX, task_fn_ref); - luaL_error(L, "Task queue overflow. Task not posted"); + n++; } + luaL_checkanyfunction(L, n); + lua_settop(L, n); + (void) luaN_posttask(L, priority); return 0; } diff --git a/app/modules/pipe.c b/app/modules/pipe.c index 7e300eaf01..322c3603d9 100644 --- a/app/modules/pipe.c +++ b/app/modules/pipe.c @@ -3,15 +3,45 @@ ** table to store the LUAL_BUFFERSIZE byte array chunks instead of the stack. ** Writing is always to the last UD in the table and overflow pushes a new UD to ** the end of the table. Reading is always from the first UD in the table and -** underrun removes the first UD to shift a new one into slot 1. +** underrun removes the first UD to shift a new one into slot 2. (Slot 1 of the +** table is reserved for the pipe reader function with 0 denoting no reader.) ** ** Reads and writes may span multiple UD buffers and if the read spans multiple UDs ** then the parts are collected as strings on the Lua stack and then concatenated -** with a `lua_concat()`. +** with a lua_concat(). ** -** Note that pipes also support the undocumented length and tostring operators -** for debugging puposes, so if p is a pipe then #p[1] gives the effective -** length of pipe slot 1 and printing p[1] gives its contents +** Note that pipe tables also support the undocumented length and tostring +** operators for debugging puposes, so if p is a pipe then #p[i] gives the +** effective length of pipe slot i and printing p[i] gives its contents. +** +** The pipe library also supports the automatic scheduling of a reader task. This +** is declared by including a Lua CB function and an optional prioirty for it to +** execute at in the pipe.create() call. The reader task may or may not empty the +** FIFO (and there is also nothing to stop the task also writing to the FIFO. The +** reader automatically reschedules itself if the pipe contains unread content. +** +** The reader tasks may be interleaved with other tasks that write to the pipe and +** others that don't. Any task writing to the pipe will also trigger the posting +** of a read task if one is not already pending. In this way at most only one +** pending reader task is pending, and this prevents overrun of the task queueing +** system. +** +** Implementation Notes: +** +** - The Pipe slot 1 is used to store the Lua CB function reference of the reader +** task. Note that is actually an auxiliary wrapper around the supplied Lua CB +** function, and this wrapper also uses upvals to store internal pipe state. +** The remaining slots are the Userdata buffer chunks. +** +** - This internal state needs to be shared with the pipe_write function, but a +** limitation of Lua 5.1 is that C functions cannot share upvals; to avoid this +** constraint, this function is also denormalised to act as the pipe_write +** function: if Arg1 is the pipe then its a pipe:write() otherwise its a +** CB wrapper. +** +** Also note that the pipe module is used by the Lua VM and therefore the create +** read, and unread methods are exposed as directly callable C functions. (Write +** is available throogh pipe[1].) ** ** Read the docs/modules/pipe.md documentation for a functional description. */ @@ -19,6 +49,8 @@ #include "module.h" #include "lauxlib.h" #include +#include "platform.h" +#include "lstate.h" #define INVALID_LEN ((unsigned)-1) @@ -31,10 +63,36 @@ typedef struct buffer { LROT_TABLE(pipe_meta) -/* Validation and utility functions */ +#define AT_TAIL 0x00 +#define AT_HEAD 0x01 +#define WRITING 0x02 + +static buffer_t *checkPipeUD (lua_State *L, int ndx); +static buffer_t *newPipeUD(lua_State *L, int ndx, int n); +static int pipe_write_aux(lua_State *L); -#define AT_HEAD 1 -#define AT_TAIL 0 +/* Validation and utility functions */ + // [-0, +0, v] +static buffer_t *checkPipeTable (lua_State *L, int tbl, int flags) { + int m = lua_gettop(L), n = lua_objlen(L, tbl); + if (lua_istable(L, tbl) && lua_getmetatable(L, tbl)) { + lua_pushrotable(L, LROT_TABLEREF(pipe_meta));/* push comparison metatable */ + if (lua_rawequal(L, -1, -2)) { /* check these match */ + buffer_t *ud; + if (n == 1) { + ud = (flags & WRITING) ? newPipeUD(L, tbl, 2) : NULL; + } else { + int i = flags & AT_HEAD ? 2 : n; /* point to head or tail of T */ + lua_rawgeti(L, tbl, i); /* and fetch UD */ + ud = checkPipeUD(L, -1); + } + lua_settop(L, m); + return ud; /* and return ptr to buffer_t rec */ + } + } + luaL_typerror(L, tbl, "pipe table"); + return NULL; /* NORETURN avoid compiler error */ +} static buffer_t *checkPipeUD (lua_State *L, int ndx) { // [-0, +0, v] buffer_t *ud = lua_touserdata(L, ndx); @@ -59,27 +117,6 @@ static buffer_t *newPipeUD(lua_State *L, int ndx, int n) { // [-0,+0,-] return ud; /* ud points to new T[#T] */ } -static buffer_t *checkPipeTable (lua_State *L, int tbl, int head) {//[-0, +0, v] - int m = lua_gettop(L), n = lua_objlen(L, tbl); - if (lua_type(L, tbl) == LUA_TTABLE && lua_getmetatable(L, tbl)) { - lua_pushrotable(L, LROT_TABLEREF(pipe_meta));/* push comparison metatable */ - if (lua_rawequal(L, -1, -2)) { /* check these match */ - buffer_t *ud; - if (n == 0) { - ud = head ? NULL : newPipeUD(L, tbl, 1); - } else { - int i = head ? 1 : n; /* point to head or tail of T */ - lua_rawgeti(L, tbl, i); /* and fetch UD */ - ud = checkPipeUD(L, -1); - } - lua_settop(L, m); - return ud; /* and return ptr to buffer_t rec */ - } - } - luaL_typerror(L, tbl, "pipe table"); - return NULL; /* NORETURN avoid compiler error */ -} - #define CHAR_DELIM -1 #define CHAR_DELIM_KEEP -2 static char getsize_delim (lua_State *L, int ndx, int *len) { // [-0, +0, v] @@ -104,22 +141,115 @@ static char getsize_delim (lua_State *L, int ndx, int *len) { // [-0, +0, v] return delim; } -/* Lua callable methods */ +/* +** Read CB Initiator AND pipe_write. If arg1 == the pipe, then this is a pipe +** write(); otherwise it is the Lua CB wapper for the task post. This botch allows +** these two functions to share Upvals within the Lua 5.1 VM: +*/ +#define UVpipe lua_upvalueindex(1) // The pipe table object +#define UVfunc lua_upvalueindex(2) // The CB's Lua function +#define UVprio lua_upvalueindex(3) // The task priority +#define UVstate lua_upvalueindex(4) // Pipe state; +#define CB_NOT_USED 0 +#define CB_ACTIVE 1 +#define CB_WRITE_UPDATED 2 +#define CB_QUIESCENT 4 +/* +** Note that nothing precludes the Lua CB function from itself writing to the +** pipe and in this case this routine will call itself recursively. +** +** The Lua CB itself takes the pipe object as a parameter and returns an optional +** boolean to force or to suppress automatic retasking if needed. If omitted, +** then the default is to repost if the pipe is not empty, otherwise the task +** chain is left to lapse. +*/ +static int pipe_write_and_read_poster (lua_State *L) { + int state = lua_tointeger(L, UVstate); + if (lua_rawequal(L, 1, UVpipe)) { + /* arg1 == the pipe, so this was invoked as a pipe_write() */ + if (pipe_write_aux(L) && state && !(state & CB_WRITE_UPDATED)) { + /* + * if this resulted in a write and not already in a CB and not already + * toggled the write update then post the task + */ + state |= CB_WRITE_UPDATED; + lua_pushinteger(L, state); + lua_replace(L, UVstate); /* Set CB state write updated flag */ + if (state == CB_QUIESCENT | CB_WRITE_UPDATED) { + lua_rawgeti(L, 1, 1); /* Get CB ref from pipe[1] */ + luaN_posttask(L, (int) lua_tointeger(L, UVprio)); /* and repost task */ + } + } -//Lua s = pipeUD:tostring() -static int pipe__tostring (lua_State *L) { - if (lua_type(L, 1) == LUA_TTABLE) { - lua_pushfstring(L, "Pipe: %p", lua_topointer(L, 1)); - } else { - buffer_t *ud = checkPipeUD(L, 1); - lua_pushlstring(L, ud->buf + ud->start, ud->end - ud->start); + } else if (state != CB_NOT_USED) { + /* invoked by the luaN_taskpost() so call the Lua CB */ + int repost; /* can take the values CB_WRITE_UPDATED or 0 */ + lua_pushinteger(L, CB_ACTIVE); /* CB state set to active only */ + lua_replace(L, UVstate); + lua_pushvalue(L, UVfunc); /* Lua CB function */ + lua_pushvalue(L, UVpipe); /* pipe table */ + lua_call(L, 1, 1); + /* + * On return from the Lua CB, the task is never reposted if the pipe is empty. + * If it is not empty then the Lua CB return status determines when reposting + * occurs: + * - true = repost + * - false = don't repost + * - nil = only repost if there has been a write update. + */ + if (lua_isboolean(L,-1)) { + repost = (lua_toboolean(L, -1) == true && + lua_objlen(L, UVpipe) > 1) ? CB_WRITE_UPDATED : 0; + } else { + repost = state & CB_WRITE_UPDATED; + } + state = CB_QUIESCENT | repost; + lua_pushinteger(L, state); /* Update the CB state */ + lua_replace(L, UVstate); + + if (repost) { + lua_rawgeti(L, UVpipe, 1); /* Get CB ref from pipe[1] */ + luaN_posttask(L, (int) lua_tointeger(L, UVprio)); /* and repost task */ + } } - return 1; + return 0; +} + +/* Lua callable methods. Since the metatable is linked to both the pipe table */ +/* and the userdata entries the __len & __tostring functions must handle both */ + +// Lua: buf = pipe.create() +int pipe_create(lua_State *L) { + int prio = -1; + lua_settop(L, 2); /* fix stack sze as 2 */ + + if (!lua_isnil(L, 1)) { + luaL_checkanyfunction(L, 1); /* non-nil arg1 must be a function */ + if (lua_isnil(L, 2)) { + prio = PLATFORM_TASK_PRIORITY_MEDIUM; + } else { + prio = (int) lua_tointeger(L, 2); + luaL_argcheck(L, prio >= PLATFORM_TASK_PRIORITY_LOW && + prio <= PLATFORM_TASK_PRIORITY_HIGH, 2, "invalid priority"); + } + } + + lua_createtable (L, 1, 0); /* create pipe table */ + lua_pushrotable(L, LROT_TABLEREF(pipe_meta)); + lua_setmetatable(L, -2); /* set pipe table's metabtable to pipe_meta */ + + lua_pushvalue(L, -1); /* UV1: pipe object */ + lua_pushvalue(L, 1); /* UV2: CB function */ + lua_pushinteger(L, prio); /* UV3: task priority */ + lua_pushinteger(L, prio == -1 ? CB_NOT_USED : CB_QUIESCENT); + lua_pushcclosure(L, pipe_write_and_read_poster, 4); /* post aux func as C task */ + lua_rawseti(L, -2, 1); /* and wrtie to T[1] */ + return 1; /* return the table */ } -// len = #pipeobj[1] +// len = #pipeobj[i] static int pipe__len (lua_State *L) { - if (lua_type(L, 1) == LUA_TTABLE) { + if (lua_type(L, 1) == LUA_TTABLE) { lua_pushinteger(L, lua_objlen(L, 1)); } else { buffer_t *ud = checkPipeUD(L, 1); @@ -128,16 +258,19 @@ static int pipe__len (lua_State *L) { return 1; } -// Lua: buf = pipe.create() -static int pipe_create(lua_State *L) { - lua_createtable (L, 1, 0); - lua_pushrotable(L, LROT_TABLEREF(pipe_meta)); - lua_setmetatable(L, 1); /* set table's metabtable to pipe_meta */ - return 1; /* return the table */ +//Lua s = pipeUD:tostring() +static int pipe__tostring (lua_State *L) { + if (lua_istable(L, 1)) { + lua_pushfstring(L, "Pipe: %p", lua_topointer(L, 1)); + } else { + buffer_t *ud = checkPipeUD(L, 1); + lua_pushlstring(L, ud->buf + ud->start, ud->end - ud->start); + } + return 1; } // Lua: rec = p:read(end_or_delim) // also [-2, +1,- ] -static int pipe_read(lua_State *L) { +int pipe_read(lua_State *L) { buffer_t *ud = checkPipeTable(L, 1, AT_HEAD); int i, k=0, n; lua_settop(L,2); @@ -158,6 +291,7 @@ static int pipe_read(lua_State *L) { want = used = i + 1 - ud->start; /* case where we've hit a delim */ if (n == CHAR_DELIM) want--; + n = 0; /* force loop exit because delim found */ } } else { want = used = (n < avail) ? n : avail; @@ -169,12 +303,12 @@ static int pipe_read(lua_State *L) { if (ud->start == ud->end) { /* shift the pipe array down overwriting T[1] */ int nUD = lua_objlen(L, 1); - for (i = 1; i < nUD; i++) { /* for i = 1, nUD-1 */ + for (i = 2; i < nUD; i++) { /* for i = 2, nUD-1 */ lua_rawgeti(L, 1, i+1); lua_rawseti(L, 1, i); /* T[i] = T[i+1] */ } lua_pushnil(L); lua_rawseti(L, 1, nUD--); /* T[n] = nil */ - if (nUD) { - lua_rawgeti(L, 1, 1); + if (nUD>1) { + lua_rawgeti(L, 1, 2); ud = checkPipeUD(L, -1); lua_pop(L, 1); } else { @@ -190,29 +324,33 @@ static int pipe_read(lua_State *L) { } // Lua: buf:unread(some_string) -static int pipe_unread(lua_State *L) { +int pipe_unread(lua_State *L) { size_t l = INVALID_LEN; const char *s = lua_tolstring(L, 2, &l); if (l==0) return 0; luaL_argcheck(L, l != INVALID_LEN, 2, "must be a string"); - buffer_t *ud = checkPipeTable(L, 1, AT_HEAD); + buffer_t *ud = checkPipeTable(L, 1, AT_HEAD | WRITING); do { - int used = ud->end - ud->start, lrem = LUAL_BUFFERSIZE-used; + int used = ud->end - ud->start; + int lrem = LUAL_BUFFERSIZE-used; if (used == LUAL_BUFFERSIZE) { + /* If the current UD is full insert a new UD at T[2] */ int i, nUD = lua_objlen(L, 1); for (i = nUD; i > 0; i--) { /* for i = nUD-1,1,-1 */ lua_rawgeti(L, 1, i); lua_rawseti(L, 1, i+1); /* T[i+1] = T[i] */ } ud = newPipeUD(L, 1, 1); used = 0; lrem = LUAL_BUFFERSIZE; - } else if (ud->end < LUAL_BUFFERSIZE) { + + } else if (ud->start < l) { + /* If the unread can't fit it before the start then shift content to end */ memmove(ud->buf + lrem, ud->buf + ud->start, used); /* must be memmove not cpy */ + ud->start = lrem; ud->end = LUAL_BUFFERSIZE; } - ud->start = lrem; ud->end = LUAL_BUFFERSIZE; if (l <= (unsigned )lrem) break; @@ -232,21 +370,24 @@ static int pipe_unread(lua_State *L) { } // Lua: buf:write(some_string) -static int pipe_write(lua_State *L) { +static int pipe_write_aux(lua_State *L) { size_t l = INVALID_LEN; const char *s = lua_tolstring(L, 2, &l); +//dbg_printf("pipe write(%u): %s", l, s); if (l==0) - return 0; + return false; luaL_argcheck(L, l != INVALID_LEN, 2, "must be a string"); - buffer_t *ud = checkPipeTable(L, 1, AT_TAIL); + buffer_t *ud = checkPipeTable(L, 1, AT_TAIL | WRITING); do { int used = ud->end - ud->start; if (used == LUAL_BUFFERSIZE) { + /* If the current UD is full insert a new UD at T[end] */ ud = newPipeUD(L, 1, lua_objlen(L, 1)+1); used = 0; - } else if (ud->start) { + } else if (LUAL_BUFFERSIZE - ud->end < l) { + /* If the write can't fit it at the end then shift content to the start */ memmove(ud->buf, ud->buf + ud->start, used); /* must be memmove not cpy */ ud->start = 0; ud->end = used; } @@ -267,7 +408,7 @@ static int pipe_write(lua_State *L) { /* Copy any residual tail to the UD buffer. Note that this is l>0 and */ memcpy(ud->buf + ud->end, s, l); ud->end += l; - return 0; + return true; } // Lua: fread = pobj:reader(1400) -- or other number @@ -289,21 +430,36 @@ static int pipe_reader(lua_State *L) { return 1; } - -LROT_BEGIN(pipe_meta) - LROT_TABENTRY( __index, pipe_meta) +LROT_BEGIN(pipe_funcs) LROT_FUNCENTRY( __len, pipe__len ) LROT_FUNCENTRY( __tostring, pipe__tostring ) LROT_FUNCENTRY( read, pipe_read ) LROT_FUNCENTRY( reader, pipe_reader ) LROT_FUNCENTRY( unread, pipe_unread ) - LROT_FUNCENTRY( write, pipe_write ) LROT_END( pipe_meta, NULL, LROT_MASK_INDEX ) +/* Using a index func is needed because the write method is at pipe[1] */ +static int pipe__index(lua_State *L) { + lua_settop(L,2); + const char *k=lua_tostring(L,2); + if(!strcmp(k,"write")){ + lua_rawgeti(L, 1, 1); + } else { + lua_pushrotable(L, LROT_TABLEREF(pipe_funcs)); + lua_replace(L, 1); + lua_rawget(L, 1); + } + return 1; +} + +LROT_BEGIN(pipe_meta) + LROT_FUNCENTRY( __index, pipe__index) + LROT_FUNCENTRY( __len, pipe__len ) + LROT_FUNCENTRY( __tostring, pipe__tostring ) +LROT_END( pipe_meta, NULL, LROT_MASK_INDEX ) LROT_BEGIN(pipe) LROT_FUNCENTRY( create, pipe_create ) LROT_END( lb, NULL, 0 ) - NODEMCU_MODULE(PIPE, "pipe", pipe, NULL); diff --git a/app/modules/somfy.c b/app/modules/somfy.c index 79abf50ef2..7ba0cd04b5 100644 --- a/app/modules/somfy.c +++ b/app/modules/somfy.c @@ -20,6 +20,7 @@ #include "lauxlib.h" #include "lmem.h" #include "platform.h" +#include "task/task.h" #include "hw_timer.h" #include "user_interface.h" diff --git a/app/modules/uart.c b/app/modules/uart.c index 5dcba1c4b0..a849095d7e 100644 --- a/app/modules/uart.c +++ b/app/modules/uart.c @@ -7,83 +7,57 @@ #include #include #include "rom.h" +#include "driver/input.h" static int uart_receive_rf = LUA_NOREF; -bool run_input = true; -bool uart_on_data_cb(const char *buf, size_t len){ - if(!buf || len==0) - return false; - if(uart_receive_rf == LUA_NOREF) - return false; + +void uart_on_data_cb(const char *buf, size_t len){ lua_State *L = lua_getstate(); - if(!L) - return false; lua_rawgeti(L, LUA_REGISTRYINDEX, uart_receive_rf); lua_pushlstring(L, buf, len); - lua_call(L, 1, 0); - return !run_input; + luaN_call(L, 1, 0, 0); } -uint16_t need_len = 0; -int16_t end_char = -1; // Lua: uart.on("method", [number/char], function, [run_input]) static int l_uart_on( lua_State* L ) { - size_t sl, el; - int32_t run = 1; - uint8_t stack = 1; - const char *method = luaL_checklstring( L, stack, &sl ); - stack++; - if (method == NULL) - return luaL_error( L, "wrong arg type" ); - - if( lua_type( L, stack ) == LUA_TNUMBER ) + size_t el; + int stack = 2, data_len = -1; + char end_char = 0; + const char *method = lua_tostring( L, 1); + bool run_input = true; + luaL_argcheck(L, method && !strcmp(method, "data"), 1, "method not supported"); + + if (lua_type( L, stack ) == LUA_TNUMBER) { - need_len = ( uint16_t )luaL_checkinteger( L, stack ); + data_len = luaL_checkinteger( L, stack ); + luaL_argcheck(L, data_len >= 0 && data_len <= LUA_MAXINPUT, stack, "wrong arg range"); stack++; - end_char = -1; - if( need_len > 255 ){ - need_len = 255; - return luaL_error( L, "wrong arg range" ); - } } - else if(lua_isstring(L, stack)) + else if (lua_isstring(L, stack)) { const char *end = luaL_checklstring( L, stack, &el ); + data_len = 0; + end_char = (int16_t) end[0]; stack++; - if(el!=1){ + if(el!=1) { return luaL_error( L, "wrong arg range" ); } - end_char = (int16_t)end[0]; - need_len = 0; } - // luaL_checkanyfunction(L, stack); - if (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION){ - if ( lua_isnumber(L, stack+1) ){ - run = lua_tointeger(L, stack+1); + if (lua_anyfunction(L, stack)) { + if (lua_isnumber(L, stack+1) && lua_tointeger(L, stack+1) == 0) { + run_input = false; } - lua_pushvalue(L, stack); // copy argument (func) to the top of stack + lua_pushvalue(L, stack); + luaL_unref(L, LUA_REGISTRYINDEX, uart_receive_rf); + uart_receive_rf = luaL_ref(L, LUA_REGISTRYINDEX); + } else { - lua_pushnil(L); - } - if(sl == 4 && strcmp(method, "data") == 0){ - run_input = true; - if(uart_receive_rf != LUA_NOREF){ - luaL_unref(L, LUA_REGISTRYINDEX, uart_receive_rf); - uart_receive_rf = LUA_NOREF; - } - if(!lua_isnil(L, -1)){ - uart_receive_rf = luaL_ref(L, LUA_REGISTRYINDEX); - if(run==0) - run_input = false; - } else { - lua_pop(L, 1); - } - }else{ - lua_pop(L, 1); - return luaL_error( L, "method not supported" ); + luaL_unref(L, LUA_REGISTRYINDEX, uart_receive_rf); + uart_receive_rf = LUA_NOREF; } + input_setup_receive(uart_on_data_cb, data_len, end_char, run_input); return 0; } @@ -91,7 +65,7 @@ bool uart0_echo = true; // Lua: actualbaud = setup( id, baud, databits, parity, stopbits, echo ) static int l_uart_setup( lua_State* L ) { - uint32_t id, databits, parity, stopbits, echo = 1; + uint32_t id, databits, parity, stopbits; uint32_t baud, res; id = luaL_checkinteger( L, 1 ); @@ -101,12 +75,8 @@ static int l_uart_setup( lua_State* L ) databits = luaL_checkinteger( L, 3 ); parity = luaL_checkinteger( L, 4 ); stopbits = luaL_checkinteger( L, 5 ); - if(lua_isnumber(L,6)){ - echo = lua_tointeger(L,6); - if(echo!=0) - uart0_echo = true; - else - uart0_echo = false; + if (lua_isnumber(L,6)) { + input_setecho(lua_tointeger(L,6) ? true : false); } res = platform_uart_setup( id, baud, databits, parity, stopbits ); diff --git a/app/platform/platform.c b/app/platform/platform.c index a343652cbd..ad7c2c26be 100644 --- a/app/platform/platform.c +++ b/app/platform/platform.c @@ -17,7 +17,8 @@ #define INTERRUPT_TYPE_IS_LEVEL(x) ((x) >= GPIO_PIN_INTR_LOLEVEL) #ifdef GPIO_INTERRUPT_ENABLE -static task_handle_t gpio_task_handle; +static platform_task_handle_t gpio_task_handle; +static int task_init_handler(void); #ifdef GPIO_INTERRUPT_HOOK_ENABLE struct gpio_hook_entry { @@ -55,11 +56,13 @@ static const int uart_bitrates[] = { BIT_RATE_3686400 }; -int platform_init() +int platform_init () { // Setup the various forward and reverse mappings for the pins get_pin_map(); + (void) task_init_handler(); + cmn_platform_init(); // All done return PLATFORM_OK; @@ -83,7 +86,7 @@ uint8_t platform_key_led( uint8_t level){ /* * Set GPIO mode to output. Optionally in RAM helper because interrupts are dsabled */ -static void NO_INTR_CODE set_gpio_no_interrupt(uint8 pin, uint8_t push_pull) { +static void NO_INTR_CODE set_gpio_no_interrupt(uint8_t pin, uint8_t push_pull) { unsigned pnum = pin_num[pin]; ETS_GPIO_INTR_DISABLE(); #ifdef GPIO_INTERRUPT_ENABLE @@ -113,7 +116,7 @@ static void NO_INTR_CODE set_gpio_no_interrupt(uint8 pin, uint8_t push_pull) { * Set GPIO mode to interrupt. Optionally RAM helper because interrupts are dsabled */ #ifdef GPIO_INTERRUPT_ENABLE -static void NO_INTR_CODE set_gpio_interrupt(uint8 pin) { +static void NO_INTR_CODE set_gpio_interrupt(uint8_t pin) { ETS_GPIO_INTR_DISABLE(); PIN_FUNC_SELECT(pin_mux[pin], pin_func[pin]); GPIO_DIS_OUTPUT(pin_num[pin]); @@ -209,9 +212,9 @@ int platform_gpio_read( unsigned pin ) #ifdef GPIO_INTERRUPT_ENABLE static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){ - uint32 j=0; - uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); - uint32 now = system_get_time(); + uint32_t j=0; + uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + uint32_t now = system_get_time(); UNUSED(dummy); #ifdef GPIO_INTERRUPT_HOOK_ENABLE @@ -244,8 +247,8 @@ static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){ GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, BIT(j)); if (diff == 0 || diff & 0x8000) { - uint32 level = 0x1 & GPIO_INPUT_GET(GPIO_ID_PIN(j)); - if (!task_post_high (gpio_task_handle, (now << 8) + (i<<1) + level)) { + uint32_t level = 0x1 & GPIO_INPUT_GET(GPIO_ID_PIN(j)); + if (!platform_post_high (gpio_task_handle, (now << 8) + (i<<1) + level)) { // If we fail to post, then try on the next interrupt pin_counter[i].seen |= 0x8000; } @@ -260,7 +263,7 @@ static void ICACHE_RAM_ATTR platform_gpio_intr_dispatcher (void *dummy){ } } -void platform_gpio_init( task_handle_t gpio_task ) +void platform_gpio_init( platform_task_handle_t gpio_task ) { gpio_task_handle = gpio_task; @@ -871,7 +874,7 @@ uint32_t platform_s_flash_write( const void *from, uint32_t toaddr, uint32_t siz memcpy(apbuf, from, size); } system_soft_wdt_feed (); - r = flash_write(toaddr, apbuf?(uint32 *)apbuf:(uint32 *)from, size); + r = flash_write(toaddr, apbuf?(uint32_t *)apbuf:(uint32_t *)from, size); if(apbuf) free(apbuf); if(SPI_FLASH_RESULT_OK == r) @@ -899,7 +902,7 @@ uint32_t platform_s_flash_read( void *to, uint32_t fromaddr, uint32_t size ) if( ((uint32_t)to) & blkmask ) { uint32_t size2=size-INTERNAL_FLASH_READ_UNIT_SIZE; - uint32* to2=(uint32*)((((uint32_t)to)&(~blkmask))+INTERNAL_FLASH_READ_UNIT_SIZE); + uint32_t* to2=(uint32_t*)((((uint32_t)to)&(~blkmask))+INTERNAL_FLASH_READ_UNIT_SIZE); r = flash_read(fromaddr, to2, size2); if(SPI_FLASH_RESULT_OK == r) { @@ -910,7 +913,7 @@ uint32_t platform_s_flash_read( void *to, uint32_t fromaddr, uint32_t size ) } } else - r = flash_read(fromaddr, (uint32 *)to, size); + r = flash_read(fromaddr, (uint32_t *)to, size); if(SPI_FLASH_RESULT_OK == r) return size; @@ -1079,3 +1082,84 @@ void* platform_print_deprecation_note( const char *msg, const char *time_frame) { printf( "Warning, deprecated API! %s. It will be removed %s. See documentation for details.\n", msg, time_frame ); } + +#define TH_MONIKER 0x68680000 +#define TH_MASK 0xFFF80000 +#define TH_UNMASK (~TH_MASK) +#define TH_SHIFT 2 +#define TH_ALLOCATION_BRICK 4 // must be a power of 2 +#define TASK_DEFAULT_QUEUE_LEN 8 +#define TASK_PRIORITY_MASK 3 +#define TASK_PRIORITY_COUNT 3 + +/* + * Private struct to hold the 3 event task queues and the dispatch callbacks + */ +static struct taskQblock { + os_event_t *task_Q[TASK_PRIORITY_COUNT]; + platform_task_callback_t *task_func; + int task_count; + } TQB = {0}; + +static void platform_task_dispatch (os_event_t *e) { + platform_task_handle_t handle = e->sig; + if ( (handle & TH_MASK) == TH_MONIKER) { + uint16_t entry = (handle & TH_UNMASK) >> TH_SHIFT; + uint8_t priority = handle & TASK_PRIORITY_MASK; + if ( priority <= PLATFORM_TASK_PRIORITY_HIGH && + TQB.task_func && + entry < TQB.task_count ){ + /* call the registered task handler with the specified parameter and priority */ + TQB.task_func[entry](e->par, priority); + return; + } + } + /* Invalid signals are ignored */ + NODE_DBG ( "Invalid signal issued: %08x", handle); +} + +/* + * Initialise the task handle callback for a given priority. + */ +static int task_init_handler (void) { + int p, qlen = TASK_DEFAULT_QUEUE_LEN; + for (p = 0; p < TASK_PRIORITY_COUNT; p++){ + TQB.task_Q[p] = (os_event_t *) c_malloc( sizeof(os_event_t)*qlen ); + if (TQB.task_Q[p]) { + os_memset(TQB.task_Q[p], 0, sizeof(os_event_t)*qlen); + system_os_task(platform_task_dispatch, p, TQB.task_Q[p], TASK_DEFAULT_QUEUE_LEN); + } else { + NODE_DBG ( "Malloc failure in platform_task_init_handler" ); + return PLATFORM_ERR; + } + } +} + + +/* + * Allocate a task handle in the relevant TCB.task_Q. Note that these Qs are resized + * as needed growing in 4 unit bricks. No GC is adopted so handles are permanently + * allocated during boot life. This isn't an issue in practice as only a few handles + * are created per priority during application init and the more volitile Lua tasks + * are allocated in the Lua registery using the luaX interface which is layered on + * this mechanism. + */ +platform_task_handle_t platform_task_get_id (platform_task_callback_t t) { + if ( (TQB.task_count & (TH_ALLOCATION_BRICK - 1)) == 0 ) { + TQB.task_func = (platform_task_callback_t *) os_realloc( + TQB.task_func, + sizeof(platform_task_callback_t) * (TQB.task_count+TH_ALLOCATION_BRICK)); + if (!TQB.task_func) { + NODE_DBG ( "Malloc failure in platform_task_get_id"); + return 0; + } + os_memset (TQB.task_func+TQB.task_count, 0, + sizeof(platform_task_callback_t)*TH_ALLOCATION_BRICK); + } + TQB.task_func[TQB.task_count++] = t; + return TH_MONIKER + ((TQB.task_count-1) << TH_SHIFT); +} + +bool platform_post (uint8 prio, platform_task_handle_t handle, platform_task_param_t par) { + return system_os_post(prio, handle | prio, par); +} diff --git a/app/platform/platform.h b/app/platform/platform.h index 3a41ddfc85..68c6811f5f 100644 --- a/app/platform/platform.h +++ b/app/platform/platform.h @@ -8,8 +8,6 @@ #include "driver/pwm.h" #include "driver/uart.h" -#include "task/task.h" - // Error / status codes enum { @@ -18,6 +16,9 @@ enum PLATFORM_UNDERFLOW = -1 }; +typedef uint32_t platform_task_handle_t; +typedef uint32_t platform_task_param_t; + // Platform initialization int platform_init(void); void platform_int_init(void); @@ -52,7 +53,7 @@ int platform_gpio_register_intr_hook(uint32_t gpio_bits, platform_hook_function #define platform_gpio_unregister_intr_hook(hook) \ platform_gpio_register_intr_hook(0, hook); void platform_gpio_intr_init( unsigned pin, GPIO_INT_TYPE type ); -void platform_gpio_init( task_handle_t gpio_task ); +void platform_gpio_init( platform_task_handle_t gpio_task ); // ***************************************************************************** // Timer subsection @@ -353,4 +354,22 @@ typedef union { uint32_t platform_rcr_read (uint8_t rec_id, void **rec); uint32_t platform_rcr_write (uint8_t rec_id, const void *rec, uint8_t size); +#define PLATFORM_TASK_PRIORITY_LOW 0 +#define PLATFORM_TASK_PRIORITY_MEDIUM 1 +#define PLATFORM_TASK_PRIORITY_HIGH 2 + +/* +* Signals are a 32-bit number of the form header:14; count:16, priority:2. The header +* is just a fixed fingerprint and the count is allocated serially by the task get_id() +* function. +*/ +#define platform_post_low(handle,param) platform_post(PLATFORM_TASK_PRIORITY_LOW, handle, param) +#define platform_post_medium(handle,param) platform_post(PLATFORM_TASK_PRIORITY_MEDIUM, handle, param) +#define platform_post_high(handle,param) platform_post(PLATFORM_TASK_PRIORITY_HIGH, handle, param) + +typedef void (*platform_task_callback_t)(platform_task_param_t param, uint8 prio); +platform_task_handle_t platform_task_get_id(platform_task_callback_t t); + +bool platform_post(uint8 prio, platform_task_handle_t h, platform_task_param_t par); + #endif diff --git a/app/platform/vfs.h b/app/platform/vfs.h index e9784e9863..cf78811ac6 100644 --- a/app/platform/vfs.h +++ b/app/platform/vfs.h @@ -15,7 +15,7 @@ // vfs_close - close file descriptor and free memory // fd: file descriptor // Returns: VFS_RES_OK or negative value in case of error -static int32_t vfs_close( int fd ) { +static inline int32_t vfs_close( int fd ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->close( f ) : VFS_RES_ERR; } @@ -25,7 +25,7 @@ static int32_t vfs_close( int fd ) { // ptr: destination data buffer // len: requested length // Returns: Number of bytes read, or VFS_RES_ERR in case of error -static int32_t vfs_read( int fd, void *ptr, size_t len ) { +static inline int32_t vfs_read( int fd, void *ptr, size_t len ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->read( f, ptr, len ) : VFS_RES_ERR; } @@ -35,7 +35,7 @@ static int32_t vfs_read( int fd, void *ptr, size_t len ) { // ptr: source data buffer // len: requested length // Returns: Number of bytes written, or VFS_RES_ERR in case of error -static int32_t vfs_write( int fd, const void *ptr, size_t len ) { +static inline sint32_t vfs_write( int fd, const void *ptr, size_t len ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->write( f, ptr, len ) : VFS_RES_ERR; } @@ -51,7 +51,7 @@ int vfs_ungetc( int c, int fd ); // VFS_SEEK_CUR - set pointer to current position + off // VFS_SEEK_END - set pointer to end of file + off // Returns: New position, or VFS_RES_ERR in case of error -static int32_t vfs_lseek( int fd, int32_t off, int whence ) { +static inline int32_t vfs_lseek( int fd, sint32_t off, int whence ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->lseek( f, off, whence ) : VFS_RES_ERR; } @@ -59,7 +59,7 @@ static int32_t vfs_lseek( int fd, int32_t off, int whence ) { // vfs_eof - test for end-of-file // fd: file descriptor // Returns: 0 if not at end, != 0 if end of file -static int32_t vfs_eof( int fd ) { +static inline int32_t vfs_eof( int fd ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->eof( f ) : VFS_RES_ERR; } @@ -67,7 +67,7 @@ static int32_t vfs_eof( int fd ) { // vfs_tell - get read/write position // fd: file descriptor // Returns: Current position -static int32_t vfs_tell( int fd ) { +static inline int32_t vfs_tell( int fd ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->tell( f ) : VFS_RES_ERR; } @@ -75,7 +75,7 @@ static int32_t vfs_tell( int fd ) { // vfs_flush - flush write cache to file // fd: file descriptor // Returns: VFS_RES_OK, or VFS_RES_ERR in case of error -static int32_t vfs_flush( int fd ) { +static inline int32_t vfs_flush( int fd ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->flush( f ) : VFS_RES_ERR; } @@ -83,7 +83,7 @@ static int32_t vfs_flush( int fd ) { // vfs_size - get current file size // fd: file descriptor // Returns: File size -static uint32_t vfs_size( int fd ) { +static inline uint32_t vfs_size( int fd ) { vfs_file *f = (vfs_file *)fd; return f ? f->fns->size( f ) : 0; } @@ -100,13 +100,13 @@ int32_t vfs_ferrno( int fd ); // vfs_closedir - close directory descriptor and free memory // dd: dir descriptor // Returns: VFS_RES_OK, or VFS_RES_ERR in case of error -static int32_t vfs_closedir( vfs_dir *dd ) { return dd->fns->close( dd ); } +static inline int32_t vfs_closedir( vfs_dir *dd ) { return dd->fns->close( dd ); } // vfs_readdir - read next directory item // dd: dir descriptor // buf: pre-allocated stat structure to be filled in // Returns: VFS_RES_OK if next item found, otherwise VFS_RES_ERR -static int32_t vfs_readdir( vfs_dir *dd, struct vfs_stat *buf ) { return dd->fns->readdir( dd, buf ); } +static inline int32_t vfs_readdir( vfs_dir *dd, struct vfs_stat *buf ) { return dd->fns->readdir( dd, buf ); } // --------------------------------------------------------------------------- // volume functions @@ -115,7 +115,7 @@ static int32_t vfs_readdir( vfs_dir *dd, struct vfs_stat *buf ) { return dd->fns // vfs_umount - unmount logical drive and free memory // vol: volume object // Returns: VFS_RES_OK, or VFS_RES_ERR in case of error -static int32_t vfs_umount( vfs_vol *vol ) { return vol->fns->umount( vol ); } +static inline int32_t vfs_umount( vfs_vol *vol ) { return vol->fns->umount( vol ); } // --------------------------------------------------------------------------- // file system functions diff --git a/app/pm/swtimer.c b/app/pm/swtimer.c index 5e0bb38b3f..cc0654a9a9 100644 --- a/app/pm/swtimer.c +++ b/app/pm/swtimer.c @@ -41,6 +41,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "task/task.h" #include "user_interface.h" #include "user_modules.h" diff --git a/app/task/Makefile b/app/task/Makefile deleted file mode 100644 index b7db4b50c0..0000000000 --- a/app/task/Makefile +++ /dev/null @@ -1,41 +0,0 @@ - -############################################################# -# Required variables for each makefile -# Discard this section from all parent makefiles -# Expected variables (with automatic defaults): -# CSRCS (all "C" files in the dir) -# SUBDIRS (all subdirs with a Makefile) -# GEN_LIBS - list of libs to be generated () -# GEN_IMAGES - list of images to be generated () -# COMPONENTS_xxx - a list of libs/objs in the form -# subdir/lib to be extracted and rolled up into -# a generated lib/image xxx.a () -# -ifndef PDIR -GEN_LIBS = libtask.a -endif - -############################################################# -# Configuration i.e. compile options etc. -# Target specific stuff (defines etc.) goes in here! -# Generally values applying to a tree are captured in the -# makefile at its root level - these are then overridden -# for a subtree within the makefile rooted therein -# -#DEFINES += - -############################################################# -# Recursion Magic - Don't touch this!! -# -# Each subtree potentially has an include directory -# corresponding to the common APIs applicable to modules -# rooted at that subtree. Accordingly, the INCLUDE PATH -# of a module can only contain the include directories up -# its parent path, and not its siblings -# -# Required for each makefile to inherit from the parent -# - -PDIR := ../$(PDIR) -sinclude $(PDIR)Makefile - diff --git a/app/task/task.c b/app/task/task.c deleted file mode 100644 index e9bc9ef630..0000000000 --- a/app/task/task.c +++ /dev/null @@ -1,72 +0,0 @@ -/** - This file encapsulates the SDK-based task handling for the NodeMCU Lua firmware. - */ -#include "task/task.h" -#include "mem.h" -#include - -#define TASK_HANDLE_MONIKER 0x68680000 -#define TASK_HANDLE_MASK 0xFFF80000 -#define TASK_HANDLE_UNMASK (~TASK_HANDLE_MASK) -#define TASK_HANDLE_SHIFT 2 -#define TASK_HANDLE_ALLOCATION_BRICK 4 // must be a power of 2 -#define TASK_DEFAULT_QUEUE_LEN 8 -#define TASK_PRIORITY_MASK 3 - -#define CHECK(p,v,msg) if (!(p)) { NODE_DBG ( msg ); return (v); } - -/* - * Private arrays to hold the 3 event task queues and the dispatch callbacks - */ -LOCAL os_event_t *task_Q[TASK_PRIORITY_COUNT]; -LOCAL task_callback_t *task_func; -LOCAL int task_count; - -LOCAL void task_dispatch (os_event_t *e) { - task_handle_t handle = e->sig; - if ( (handle & TASK_HANDLE_MASK) == TASK_HANDLE_MONIKER) { - uint16 entry = (handle & TASK_HANDLE_UNMASK) >> TASK_HANDLE_SHIFT; - uint8 priority = handle & TASK_PRIORITY_MASK; - if ( priority <= TASK_PRIORITY_HIGH && task_func && entry < task_count ){ - /* call the registered task handler with the specified parameter and priority */ - task_func[entry](e->par, priority); - return; - } - } - /* Invalid signals are ignored */ - NODE_DBG ( "Invalid signal issued: %08x", handle); -} - -/* - * Initialise the task handle callback for a given priority. This doesn't need - * to be called explicitly as the get_id function will call this lazily. - */ -bool task_init_handler(uint8 priority, uint8 qlen) { - if (priority <= TASK_PRIORITY_HIGH && task_Q[priority] == NULL) { - task_Q[priority] = (os_event_t *) os_malloc( sizeof(os_event_t)*qlen ); - os_memset (task_Q[priority], 0, sizeof(os_event_t)*qlen); - if (task_Q[priority]) { - return system_os_task( task_dispatch, priority, task_Q[priority], qlen ); - } - } - return false; -} - -task_handle_t task_get_id(task_callback_t t) { - int p = TASK_PRIORITY_COUNT; - /* Initialise and uninitialised Qs with the default Q len */ - while(p--) if (!task_Q[p]) { - CHECK(task_init_handler( p, TASK_DEFAULT_QUEUE_LEN ), 0, "Task initialisation failed"); - } - - if ( (task_count & (TASK_HANDLE_ALLOCATION_BRICK - 1)) == 0 ) { - /* With a brick size of 4 this branch is taken at 0, 4, 8 ... and the new size is +4 */ - task_func =(task_callback_t *) os_realloc(task_func, - sizeof(task_callback_t)*(task_count+TASK_HANDLE_ALLOCATION_BRICK)); - CHECK(task_func, 0 , "Malloc failure in task_get_id"); - os_memset (task_func+task_count, 0, sizeof(task_callback_t)*TASK_HANDLE_ALLOCATION_BRICK); - } - - task_func[task_count++] = t; - return TASK_HANDLE_MONIKER + ((task_count-1) << TASK_HANDLE_SHIFT); -} diff --git a/app/user/user_main.c b/app/user/user_main.c index 9afb1fb8d1..ef717ece82 100644 --- a/app/user/user_main.c +++ b/app/user/user_main.c @@ -20,6 +20,7 @@ #include "ets_sys.h" #include "driver/uart.h" +#include "driver/input.h" #include "task/task.h" #include "mem.h" #include "espconn.h" @@ -29,9 +30,6 @@ #include "rtc/rtctime.h" #endif -static task_handle_t input_sig; -static uint8 input_sig_flag = 0; - /* Contents of esp_init_data_default.bin */ extern const uint32_t init_data[], init_data_end[]; #define INIT_DATA_SIZE ((init_data_end - init_data)*sizeof(uint32_t)) @@ -275,40 +273,11 @@ uint32 ICACHE_RAM_ATTR user_iram_memory_is_enabled(void) { return FALSE; // NodeMCU runs like a dog if iRAM is enabled } -// +================== New task interface ==================+ -static void start_lua(task_param_t param, uint8 priority) { - char* lua_argv[] = { (char *)"lua", (char *)"-i", NULL }; - NODE_DBG("Task task_lua started.\n"); - lua_main( 2, lua_argv ); - // Only enable UART interrupts once we've successfully started up, - // otherwise the task queue might fill up with input events and prevent - // the start_lua task from being posted. - ETS_UART_INTR_ENABLE(); -} - -static void handle_input(task_param_t flag, uint8 priority) { - (void)priority; - if (flag & 0x8000) { - input_sig_flag = flag & 0x4000 ? 1 : 0; - } - lua_handle_input (flag & 0x01); -} - -bool user_process_input(bool force) { - return task_post_low(input_sig, force); -} - void nodemcu_init(void) { - NODE_ERR("\n"); - // Initialize platform first for lua modules. - if( platform_init() != PLATFORM_OK ) - { - // This should never happen - NODE_DBG("Can not init platform for modules.\n"); - return; - } - if (!task_post_low(task_get_id(start_lua),'s')) - NODE_ERR("Failed to post the start_lua task!\n"); + NODE_DBG("Task task_lua starting.\n"); + // Call the Lua bootstrap startup directly. This uses the task interface + // internally to carry out the main lua libraries initialisation. + lua_main(); } #ifdef LUA_USE_MODULES_WIFI @@ -328,18 +297,17 @@ void user_rf_pre_init(void) * Parameters : none * Returns : none *******************************************************************************/ -void user_init(void) -{ - +void user_init(void) { #ifdef LUA_USE_MODULES_RTCTIME rtctime_late_startup (); #endif - + if( platform_init() != PLATFORM_OK ) { + // This should never happen + NODE_DBG("Can not init platform for modules.\n"); + return; + } UartBautRate br = BIT_RATE_DEFAULT; - - input_sig = task_get_id(handle_input); - uart_init (br, br, input_sig, &input_sig_flag); - + uart_init (br, br); #ifndef NODE_DEBUG system_set_os_print(0); #endif diff --git a/docs/modules/node.md b/docs/modules/node.md index deaa007e83..326cd48bb5 100644 --- a/docs/modules/node.md +++ b/docs/modules/node.md @@ -298,11 +298,7 @@ print("NodeMCU "..majorVer.."."..minorVer.."."..devVer) ## node.input() -Submits a string to the Lua interpreter. Similar to `pcall(loadstring(str))`, but without the single-line limitation. - -!!! attention - - This function only has an effect when invoked from a callback. Using it directly on the console **does not work**. +Submits a string to the Lua interpreter. Similar to `pcall(loadstring(str))`, but without the single-line limitation. Note that the Line interpreter only actions complete Lua chunks. A Lue Lua chunk must comprise one or more complete `'\n'` terminaed lines that form a complete compilation unit. #### Syntax `node.input(str)` @@ -317,56 +313,29 @@ Submits a string to the Lua interpreter. Similar to `pcall(loadstring(str))`, bu ```lua sk:on("receive", function(conn, payload) node.input(payload) end) ``` +See the `telnet/telnet.lua` in `lua_examples` for a more comprehensive example. #### See also [`node.output()`](#nodeoutput) ## node.output() -Redirects the Lua interpreter output to a callback function. Optionally also prints it to the serial console. - -!!! caution - - 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. +Redirects the Lua interpreter to a `stdout` pipe when a CB function is specified (See `pipe` module) and resets output to normal otherwise. Optionally also prints to the serial console. #### Syntax -`node.output(function(str), serial_debug)` +`node.output(function(pipe), serial_debug)` #### Parameters - - `output_fn(str)` a function accept every output as str, and can send the output to a socket (or maybe a file). + - `output_fn(pipe)` a function accept every output as str, and can send the output to a socket (or maybe a file). Note that this function must conform to the fules for a pipe reader callback. - `serial_debug` 1 output also show in serial. 0: no serial output. #### Returns `nil` #### Example -```lua -function tonet(str) - sk:send(str) -end -node.output(tonet, 1) -- serial also get the Lua output. -``` -```lua --- a simple telnet server -s=net.createServer(net.TCP) -s:listen(2323,function(c) - con_std = c - function s_output(str) - if(con_std~=nil) - then con_std:send(str) - end - end - node.output(s_output, 0) -- re-direct output to function s_ouput. - c:on("receive",function(c,l) - node.input(l) -- works like pcall(loadstring(l)) but support multiple separate line - end) - c:on("disconnection",function(c) - con_std = nil - node.output(nil) -- un-regist the redirect output function, output goes to serial - end) -end) -``` +See the `telnet/telnet.lua` in `lua_examples` for a more comprehensive example of its use. + #### See also [`node.input()`](#nodeinput) diff --git a/docs/modules/pipe.md b/docs/modules/pipe.md index 2eb87fa4b1..439276007a 100644 --- a/docs/modules/pipe.md +++ b/docs/modules/pipe.md @@ -11,15 +11,15 @@ task to another. Create a pipe. #### Syntax -`pobj = pipe.create()` +`pobj = pipe.create([CB_function],[task_priority])` #### Parameters -None +- `CB_function` optional reader callback which is called through the `ǹode.task.post()` when the pipe is written to. If the CB returns a boolean, then the reposting action is forced: it is reposted if true and not if false. If the return is nil or omitted then the deault is to repost if a pipe write has occured since the last call. +- `task_priority` See `ǹode.task.post()` #### Returns A pipe resource. - ## pobj:read() Read a record from a pipe object. From ba03cb0ef4bec5ba6758af8bb30bd5a62e929488 Mon Sep 17 00:00:00 2001 From: Terry Ellison Date: Fri, 19 Jul 2019 03:53:53 +0100 Subject: [PATCH 02/27] Add telnet example --- lua_examples/telnet/README.md | 51 ++++---------- lua_examples/telnet/simple_telnet.lua | 35 ---------- .../{telnet.lua => telnet_fifosock.lua} | 0 lua_examples/telnet/telnet_pipe.lua | 68 +++++++++++++++++++ 4 files changed, 82 insertions(+), 72 deletions(-) delete mode 100644 lua_examples/telnet/simple_telnet.lua rename lua_examples/telnet/{telnet.lua => telnet_fifosock.lua} (100%) create mode 100644 lua_examples/telnet/telnet_pipe.lua diff --git a/lua_examples/telnet/README.md b/lua_examples/telnet/README.md index 5029da2978..85075c7b75 100644 --- a/lua_examples/telnet/README.md +++ b/lua_examples/telnet/README.md @@ -6,42 +6,19 @@ | 2018-05-24 | [Terry Ellison](https://github.com/TerryE) | [Terry Ellison](https://github.com/TerryE) | [telnet.lua](./telnet.lua) | -The Lua telnet example previously provided in our distro has been moved to this -file `simple_telnet.lua` in this folder. This README discusses the version complex -implementation at the Lua module `telnet.lua`. The main reason for this complex -alternative is that a single Lua command can produce a LOT of output, and the -telnet server has to work within four constraints: - -- The SDK rules are that you can only issue one send per task invocation, so any -overflow must be buffered, and the buffer emptied using an on:sent callback (CB). - -- Since the interpeter invokes a node.output CB per field, you have a double whammy -that these fields are typically small, so using a simple array FIFO would rapidly -exhaust RAM. - -- For network efficiency, the module aggregates any FIFO buffered into sensible -sized packet, say 1024 bytes, but it must also need to handle the case when larger -string span multiple packets. However, you must flush the buffer if necessary. - -- The overall buffering strategy needs to be reasonably memory efficient and avoid -hitting the GC too hard, so where practical avoid aggregating small strings to more -than 256 chars (as NodeMCU handles \<256 using stack buffers), and avoid serial -aggregation such as buf = buf .. str as this hammers the GC. - -So this server adopts a simple buffering scheme using a two level FIFO. The -`node.output` CB adds records to the 1st level FIFO until the #recs is \> 32 or the -total size would exceed 256 bytes. Once over this threashold, the contents of the -FIFO are concatenated into a 2nd level FIFO entry of upto 256 bytes, and the 1st -level FIFO cleared down to any residue. - -The sender dumps the 2nd level FIFO aggregating records up to 1024 bytes and once this -is empty dumps an aggrate of the 1st level. - -Lastly remember that owing to architectural limitations of the firmware, this server -can only service stdin and stdout. Lua errors are still sent to stderr which is -the UART0 device. Hence errors will fail silently. If you want to capture -errors then you will need to wrap any commands in a `pcall()` and print any -error return. +This README discusses the packet marshalling versions of telnet. The first (fifosock) +version was written for SDK 2 implementations, with all of the marshalling imlemented +in Lua; the second (pipe) version uses the latest features added to the SDK 3 version +that have been added to prepare for the `lua53` implementation. These exploit the +stdin / stdout pipe functionality and task integration that is now build into the +NodeNMCU Lua core. + +There are two nice advantages of this core implementation: + +- Errors are now written to stdout in a spearate task execution. +- The pipes pretty much eliminate uart and telnet overrun. + +Both have the same interface if required into the variable `telnet` ## telnet:open() @@ -64,7 +41,7 @@ Nothing returned (this is evaluted as `nil` in a scalar context). ## telnet:close() -Close a telnet server and release all resources. +Close a telnet server and release all resources. Also set the variable `telnet` to nil to fully reference and GC the resources. #### Syntax diff --git a/lua_examples/telnet/simple_telnet.lua b/lua_examples/telnet/simple_telnet.lua deleted file mode 100644 index 3f9525bbac..0000000000 --- a/lua_examples/telnet/simple_telnet.lua +++ /dev/null @@ -1,35 +0,0 @@ --- a simple telnet server - -telnet_srv = net.createServer(net.TCP, 180) -telnet_srv:listen(2323, function(socket) - local fifo = {} - local fifo_drained = true - - local function sender(c) - if #fifo > 0 then - c:send(table.remove(fifo, 1)) - else - fifo_drained = true - end - end - - local function s_output(str) - table.insert(fifo, str) - if socket ~= nil and fifo_drained then - fifo_drained = false - sender(socket) - end - end - - node.output(s_output, 0) -- re-direct output to function s_ouput. - - socket:on("receive", function(c, l) - node.input(l) -- works like pcall(loadstring(l)) but support multiple separate line - end) - socket:on("disconnection", function(c) - node.output(nil) -- un-regist the redirect output function, output goes to serial - end) - socket:on("sent", sender) - - print("Welcome to NodeMCU world.") -end) diff --git a/lua_examples/telnet/telnet.lua b/lua_examples/telnet/telnet_fifosock.lua similarity index 100% rename from lua_examples/telnet/telnet.lua rename to lua_examples/telnet/telnet_fifosock.lua diff --git a/lua_examples/telnet/telnet_pipe.lua b/lua_examples/telnet/telnet_pipe.lua new file mode 100644 index 0000000000..935f5837fe --- /dev/null +++ b/lua_examples/telnet/telnet_pipe.lua @@ -0,0 +1,68 @@ +--[[ A telnet server T. Ellison, June 2019 + +This version of the telnet server demonstrates the use of the new stdin and stout +pipes, which is a C implementation of the Lua fifosock concept moved into the +Lua core. These two pipes are referenced in the Lua registry. + +]] + +local M = {} +local modname = ... +local function telnet_session(socket) + local node = node + local stdout, sending + + local function output_CB(opipe) -- upval: socket + stdout = opipe + local rec = opipe:read(1400) + if rec and #rec > 0 then socket:send(rec) end + return false -- don't repost as the on:sent will do this + end + + local function onsent_CB(skt) -- upval: stdout + local rec = stdout:read(1400) + if rec and #rec > 0 then skt:send(rec) end + end + + local function disconnect_CB(skt) -- upval: socket, stdout + node.output() + socket, stdout = nil, nil -- set upvals to nl to allow GC + end + + node.output(output_CB, 0) + socket:on("receive", function(_,rec) node.input(rec) end) + socket:on("sent", onsent_CB) + socket:on("disconnection", disconnect_CB) + print(("Welcome to NodeMCU world (%d mem free, %s)"):format( + node.heap(), wifi.sta.getip())) +end + +function M.open(this, ssid, pwd, port) + local tmr, wifi, uwrite = tmr, wifi, uart.write + if ssid then + wifi.setmode(wifi.STATION, false) + wifi.sta.config { ssid = ssid, pwd = pwd, save = false } + end + local t = tmr.create() + t:alarm(500, tmr.ALARM_AUTO, function() + if (wifi.sta.status() == wifi.STA_GOTIP) then + t:unregister() + t=nil + print(("Telnet server started (%d mem free, %s)"):format( + node.heap(), wifi.sta.getip())) + M.svr = net.createServer(net.TCP, 180) + M.svr:listen(port or 23, telnet_session) + else + uwrite(0,".") + end + end) +end + +function M.close(this) + if this.svr then this.svr:close() end + package.loaded[modname] = nil +end + +return M + + From bc98174e82f0f5147ea5115fbaec656857c35b3f Mon Sep 17 00:00:00 2001 From: Terry Ellison Date: Sat, 20 Jul 2019 00:45:08 +0100 Subject: [PATCH 03/27] Updates following JM review --- app/driver/uart.c | 26 +------------------------ app/include/driver/input.h | 1 - app/lua/lflash.c | 5 +++-- app/lua/lnodemcu.c | 12 ++++-------- app/modules/node.c | 2 +- app/modules/uart.c | 2 +- lua_examples/telnet/telnet_fifosock.lua | 7 ++++--- lua_examples/telnet/telnet_pipe.lua | 13 +++++++------ 8 files changed, 21 insertions(+), 47 deletions(-) diff --git a/app/driver/uart.c b/app/driver/uart.c index 5b50d194b9..ff93f0283c 100644 --- a/app/driver/uart.c +++ b/app/driver/uart.c @@ -165,31 +165,7 @@ uart_tx_one_char(uint8 uart, uint8 TxChar) WRITE_PERI_REG(UART_FIFO(uart) , TxChar); return OK; } -#if 0 -/****************************************************************************** - * FunctionName : uart1_write_char - * Description : Internal used function - * Do some special deal while tx char is '\r' or '\n' - * Parameters : char c - character to tx - * Returns : NONE -*******************************************************************************/ -LOCAL void ICACHE_FLASH_ATTR -uart1_write_char(char c) -{ - if (c == '\n') - { - uart_tx_one_char(UART1, '\r'); - uart_tx_one_char(UART1, '\n'); - } - else if (c == '\r') - { - } - else - { - uart_tx_one_char(UART1, c); - } -} -#endif + /****************************************************************************** * FunctionName : uart0_tx_buffer * Description : use uart0 to transfer buffer diff --git a/app/include/driver/input.h b/app/include/driver/input.h index f18f48b6af..6b44591027 100644 --- a/app/include/driver/input.h +++ b/app/include/driver/input.h @@ -6,6 +6,5 @@ extern void input_setup(int bufsize, const char *prompt); extern void input_setup_receive(uart_cb_t uart_on_data_cb, int data_len, char end_char, bool run_input); extern void input_setecho (bool flag); extern void input_setprompt (const char *prompt); -extern void input_process_arm(void); #endif /* READLINE_APP_H */ diff --git a/app/lua/lflash.c b/app/lua/lflash.c index d2bf5cc2d5..340c106656 100644 --- a/app/lua/lflash.c +++ b/app/lua/lflash.c @@ -121,7 +121,7 @@ static char *flashSetPosition(uint32_t offset){ static char *flashBlock(const void* b, size_t size) { void *cur = flashPosition(); - NODE_DBG("flashBlock((%04x),%08x,%04x)\n", curOffset,b,size); + NODE_DBG("flashBlock((%04x),%p,%04x)\n", curOffset,b,size); lua_assert(ALIGN_BITS(b) == 0 && ALIGN_BITS(size) == 0); platform_flash_write(b, flashAddrPhys+curOffset, size); curOffset += size; @@ -451,7 +451,8 @@ void procSecondPass (void) { int i, len = (out->ndx > out->flashLen) ? (out->flashLen % WRITE_BLOCKSIZE) / WORDSIZE : WRITE_BLOCKSIZE / WORDSIZE; - uint32_t *buf = (uint32_t *) out->buffer.byte, flags = 0; + uint32_t *buf = (uint32_t *) out->buffer.byte; + uint32_t flags = 0; /* * Relocate all the addresses tagged in out->flags. This can't be done in * place because the out->blocks are still in use as dictionary content so diff --git a/app/lua/lnodemcu.c b/app/lua/lnodemcu.c index 6d8cf1a236..520ea02df9 100644 --- a/app/lua/lnodemcu.c +++ b/app/lua/lnodemcu.c @@ -22,12 +22,6 @@ #include "platform.h" extern int debug_errorfb (lua_State *L); -#if 0 -extern int pipe_create(lua_State *L); -extern int pipe_read(lua_State *L); -extern int pipe_unread(lua_State *L); -extern int pipe_write(lua_State *L); -#endif /* ** Error Reporting Task. We can't pass a string parameter to the error reporter ** directly through the task interface the call is wrapped in a C closure with @@ -62,13 +56,15 @@ int luaN_traceback (lua_State *L) { ** an error handler which will catch any error and then post this to the ** registered reporter function as a separate follow-on task. */ -int luaN_call (lua_State *L, int narg, int res, int doGC) { // [-narg, +0, v] +int luaN_call (lua_State *L, int narg, int nres, int doGC) { // [-narg, +0, v] int status; int base = lua_gettop(L) - narg; lua_pushcfunction(L, luaN_traceback); lua_insert(L, base); /* put under args */ - status = lua_pcall(L, narg, (res < 0 ? LUA_MULTRET : res), base); + status = lua_pcall(L, narg, (nres < 0 ? LUA_MULTRET : nres), base); lua_remove(L, base); /* remove traceback function */ + if (status && nres >=0) + lua_settop(L, base + nres); /* balance the stack on error */ /* force a complete garbage collection if requested */ if (doGC) lua_gc(L, LUA_GCCOLLECT, 0); diff --git a/app/modules/node.c b/app/modules/node.c index d3f59a4bcd..bce8ca28e8 100644 --- a/app/modules/node.c +++ b/app/modules/node.c @@ -215,7 +215,7 @@ static int node_output( lua_State* L ) lua_pushlightfunction(L, &pipe_create); lua_insert(L, 1); lua_pushinteger(L, LUA_TASK_MEDIUM); - lua_call(L, 2, 1); /* T[1] = pipe.create(dojob, low_priority) */ + lua_call(L, 2, 1); /* T[1] = pipe.create(CB, medium_priority) */ } else { // remove the stdout pipe lua_pop(L,1); lua_pushnil(L); /* T[1] = nil */ diff --git a/app/modules/uart.c b/app/modules/uart.c index a849095d7e..e67b1ea799 100644 --- a/app/modules/uart.c +++ b/app/modules/uart.c @@ -31,7 +31,7 @@ static int l_uart_on( lua_State* L ) if (lua_type( L, stack ) == LUA_TNUMBER) { data_len = luaL_checkinteger( L, stack ); - luaL_argcheck(L, data_len >= 0 && data_len <= LUA_MAXINPUT, stack, "wrong arg range"); + luaL_argcheck(L, data_len >= 0 && data_len < LUA_MAXINPUT, stack, "wrong arg range"); stack++; } else if (lua_isstring(L, stack)) diff --git a/lua_examples/telnet/telnet_fifosock.lua b/lua_examples/telnet/telnet_fifosock.lua index 328d5cbc75..318fe5daf9 100644 --- a/lua_examples/telnet/telnet_fifosock.lua +++ b/lua_examples/telnet/telnet_fifosock.lua @@ -27,10 +27,11 @@ concatenated into a 2nd level FIFO entry of upto 256 bytes, and the 1st level FI cleared down to any residue. ]] -local node, table, tmr, wifi, uwrite, tostring = - node, table, tmr, wifi, uart.write, tostring +--luacheck: no unused args -local function telnet_listener(socket) +local node, tmr, wifi, uwrite = node, tmr, wifi, uart.write + +local function telnet_listener(socket) local queueLine = (require "fifosock").wrap(socket) local function receiveLine(s, line) diff --git a/lua_examples/telnet/telnet_pipe.lua b/lua_examples/telnet/telnet_pipe.lua index 935f5837fe..e33be27763 100644 --- a/lua_examples/telnet/telnet_pipe.lua +++ b/lua_examples/telnet/telnet_pipe.lua @@ -1,16 +1,17 @@ --[[ A telnet server T. Ellison, June 2019 -This version of the telnet server demonstrates the use of the new stdin and stout -pipes, which is a C implementation of the Lua fifosock concept moved into the +This version of the telnet server demonstrates the use of the new stdin and stout +pipes, which is a C implementation of the Lua fifosock concept moved into the Lua core. These two pipes are referenced in the Lua registry. ]] +--luacheck: no unused args local M = {} local modname = ... -local function telnet_session(socket) +local function telnet_session(socket) local node = node - local stdout, sending + local stdout local function output_CB(opipe) -- upval: socket stdout = opipe @@ -31,8 +32,8 @@ local function telnet_session(socket) node.output(output_CB, 0) socket:on("receive", function(_,rec) node.input(rec) end) - socket:on("sent", onsent_CB) - socket:on("disconnection", disconnect_CB) + socket:on("sent", onsent_CB) + socket:on("disconnection", disconnect_CB) print(("Welcome to NodeMCU world (%d mem free, %s)"):format( node.heap(), wifi.sta.getip())) end From 39bb60e75ae31ed02f611dc384cc5ad3a6bd9f6e Mon Sep 17 00:00:00 2001 From: Terry Ellison Date: Tue, 23 Jul 2019 20:46:49 +0100 Subject: [PATCH 04/27] Rebased against current dev --- app/driver/pwm2.c | 2 ++ app/include/pm/pmSleep.h | 2 +- app/lua/lflash.c | 1 + app/modules/bme280.c | 1 + app/modules/bme680.c | 1 + app/modules/bmp085.c | 1 + app/modules/file.c | 2 +- app/modules/hdc1080.c | 1 + app/modules/rc.c | 2 ++ app/modules/rotary.c | 1 + app/modules/tcs34725.c | 3 ++- app/modules/uart.c | 2 +- app/modules/wps.c | 1 + app/platform/platform.c | 4 ++-- app/platform/sdcard.c | 2 +- 15 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/driver/pwm2.c b/app/driver/pwm2.c index 38da5c21c7..a052ca3009 100644 --- a/app/driver/pwm2.c +++ b/app/driver/pwm2.c @@ -7,11 +7,13 @@ #include #include +#include #include "mem.h" #include "pin_map.h" #include "platform.h" #include "hw_timer.h" #include "driver/pwm2.h" +#include "user_interface.h" #define PWM2_TMR_MAGIC_80MHZ 16 #define PWM2_TMR_MAGIC_160MHZ 32 diff --git a/app/include/pm/pmSleep.h b/app/include/pm/pmSleep.h index 2fb8f46cf2..ea6c46d492 100644 --- a/app/include/pm/pmSleep.h +++ b/app/include/pm/pmSleep.h @@ -15,7 +15,7 @@ #if defined(PMSLEEP_DEBUG) #define PMSLEEP_DBG(fmt, ...) dbg_printf("\tPMSLEEP(%s):"fmt"\n", __FUNCTION__, ##__VA_ARGS__) #else - #define PMSLEEP_DBG(...) //c_printf(__VA_ARGS__) + #define PMSLEEP_DBG(...) //printf(__VA_ARGS__) #endif #if defined(NODE_ERROR) diff --git a/app/lua/lflash.c b/app/lua/lflash.c index 340c106656..221a667062 100644 --- a/app/lua/lflash.c +++ b/app/lua/lflash.c @@ -14,6 +14,7 @@ #include "lfunc.h" #include "lflash.h" #include "platform.h" +#include "user_interface.h" #include "vfs.h" #include "uzlib.h" diff --git a/app/modules/bme280.c b/app/modules/bme280.c index e8525921c8..9379445e97 100644 --- a/app/modules/bme280.c +++ b/app/modules/bme280.c @@ -11,6 +11,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include /****************************************************/ diff --git a/app/modules/bme680.c b/app/modules/bme680.c index 85efbe2068..dcf6b80059 100644 --- a/app/modules/bme680.c +++ b/app/modules/bme680.c @@ -9,6 +9,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include #include "bme680_defs.h" diff --git a/app/modules/bmp085.c b/app/modules/bmp085.c index 97eeadfb43..75caf2e070 100644 --- a/app/modules/bmp085.c +++ b/app/modules/bmp085.c @@ -1,6 +1,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include #include diff --git a/app/modules/file.c b/app/modules/file.c index 34ff3ed4a3..db717076da 100644 --- a/app/modules/file.c +++ b/app/modules/file.c @@ -585,7 +585,7 @@ static int file_putfile( lua_State* L ) // Lua: fsinfo() static int file_fsinfo( lua_State* L ) { - u32_t total, used; + uint32_t total, used; if (vfs_fsinfo("", &total, &used)) { return luaL_error(L, "file system failed"); } diff --git a/app/modules/hdc1080.c b/app/modules/hdc1080.c index bc3f995f80..55d6eddb76 100644 --- a/app/modules/hdc1080.c +++ b/app/modules/hdc1080.c @@ -7,6 +7,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include #include #include diff --git a/app/modules/rc.c b/app/modules/rc.c index 4f4c85e689..c65061a712 100644 --- a/app/modules/rc.c +++ b/app/modules/rc.c @@ -1,7 +1,9 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include "rom.h" + //#include "driver/easygpio.h" //static Ping_Data pingA; #define defPulseLen 185 diff --git a/app/modules/rotary.c b/app/modules/rotary.c index 44397ef72d..7a414e9cf1 100644 --- a/app/modules/rotary.c +++ b/app/modules/rotary.c @@ -9,6 +9,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "task/task.h" #include #include #include "user_interface.h" diff --git a/app/modules/tcs34725.c b/app/modules/tcs34725.c index b5d59f28df..d989c02241 100644 --- a/app/modules/tcs34725.c +++ b/app/modules/tcs34725.c @@ -22,6 +22,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" #include // #define TCS34725_ADDRESS (0x29<<1) @@ -352,4 +353,4 @@ LROT_BEGIN(tcs34725) LROT_END( tcs34725, NULL, 0 ) -NODEMCU_MODULE(TCS34725, "tcs34725", tcs34725, NULL); \ No newline at end of file +NODEMCU_MODULE(TCS34725, "tcs34725", tcs34725, NULL); diff --git a/app/modules/uart.c b/app/modules/uart.c index e67b1ea799..2b1f916488 100644 --- a/app/modules/uart.c +++ b/app/modules/uart.c @@ -45,7 +45,7 @@ static int l_uart_on( lua_State* L ) } } - if (lua_anyfunction(L, stack)) { + if (lua_isanyfunction(L, stack)) { if (lua_isnumber(L, stack+1) && lua_tointeger(L, stack+1) == 0) { run_input = false; } diff --git a/app/modules/wps.c b/app/modules/wps.c index e23f6be55c..43b52e2867 100644 --- a/app/modules/wps.c +++ b/app/modules/wps.c @@ -4,6 +4,7 @@ #include "module.h" #include "lauxlib.h" #include "platform.h" +#include "user_interface.h" static int wps_callback_ref; diff --git a/app/platform/platform.c b/app/platform/platform.c index ad7c2c26be..47644dbbc0 100644 --- a/app/platform/platform.c +++ b/app/platform/platform.c @@ -1124,7 +1124,7 @@ static void platform_task_dispatch (os_event_t *e) { static int task_init_handler (void) { int p, qlen = TASK_DEFAULT_QUEUE_LEN; for (p = 0; p < TASK_PRIORITY_COUNT; p++){ - TQB.task_Q[p] = (os_event_t *) c_malloc( sizeof(os_event_t)*qlen ); + TQB.task_Q[p] = (os_event_t *) malloc( sizeof(os_event_t)*qlen ); if (TQB.task_Q[p]) { os_memset(TQB.task_Q[p], 0, sizeof(os_event_t)*qlen); system_os_task(platform_task_dispatch, p, TQB.task_Q[p], TASK_DEFAULT_QUEUE_LEN); @@ -1146,7 +1146,7 @@ static int task_init_handler (void) { */ platform_task_handle_t platform_task_get_id (platform_task_callback_t t) { if ( (TQB.task_count & (TH_ALLOCATION_BRICK - 1)) == 0 ) { - TQB.task_func = (platform_task_callback_t *) os_realloc( + TQB.task_func = (platform_task_callback_t *) realloc( TQB.task_func, sizeof(platform_task_callback_t) * (TQB.task_count+TH_ALLOCATION_BRICK)); if (!TQB.task_func) { diff --git a/app/platform/sdcard.c b/app/platform/sdcard.c index 143d96c89b..d2858612b9 100644 --- a/app/platform/sdcard.c +++ b/app/platform/sdcard.c @@ -1,7 +1,7 @@ #include "platform.h" #include "driver/spi.h" #include - +#include "user_interface.h" #include "sdcard.h" From 522b1d00767561c7e30bbad714266f78a1072e88 Mon Sep 17 00:00:00 2001 From: Terry Ellison Date: Tue, 23 Jul 2019 21:22:59 +0100 Subject: [PATCH 05/27] Rebased against current dev, tweaks for clean compile --- app/coap/endpoints.c | 12 +++++------- app/lua/lua.h | 2 +- app/modules/http.c | 1 + app/platform/u8x8_nodemcu_hal.c | 1 + app/platform/ucg_nodemcu_hal.c | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/coap/endpoints.c b/app/coap/endpoints.c index 1ec882874b..ce62dda6b3 100644 --- a/app/coap/endpoints.c +++ b/app/coap/endpoints.c @@ -162,8 +162,6 @@ static int handle_post_function(const coap_endpoint_t *ep, coap_rw_buffer_t *scr return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_NOT_FOUND, COAP_CONTENTTYPE_NONE); } -extern int lua_put_line(const char *s, size_t l); - static const coap_endpoint_path_t path_command = {2, {"v1", "c"}}; static int handle_post_command(const coap_endpoint_t *ep, coap_rw_buffer_t *scratch, const coap_packet_t *inpkt, coap_packet_t *outpkt, uint8_t id_hi, uint8_t id_lo) { @@ -171,11 +169,11 @@ static int handle_post_command(const coap_endpoint_t *ep, coap_rw_buffer_t *scra return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_BAD_REQUEST, COAP_CONTENTTYPE_TEXT_PLAIN); if (inpkt->payload.len > 0) { - char line[LUA_MAXINPUT]; - if (!coap_buffer_to_string(line, LUA_MAXINPUT, &inpkt->payload) && - lua_put_line(line, strlen(line))) { - NODE_DBG("\nResult(if any):\n"); - system_os_post (LUA_TASK_PRIO, LUA_PROCESS_LINE_SIG, 0); + char line[LUA_MAXINPUT+1]; + if (!coap_buffer_to_string(line, LUA_MAXINPUT, &inpkt->payload)) { + int l = strlen(line); + line[l] = '\n'; + lua_input_string(line, l+1); } return coap_make_response(scratch, outpkt, NULL, 0, id_hi, id_lo, &inpkt->tok, COAP_RSPCODE_CONTENT, COAP_CONTENTTYPE_TEXT_PLAIN); } diff --git a/app/lua/lua.h b/app/lua/lua.h index 874f0fc43c..46b5dc3517 100644 --- a/app/lua/lua.h +++ b/app/lua/lua.h @@ -277,7 +277,7 @@ LUA_API void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); #define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE) #define lua_isrotable(L,n) (lua_type(L, (n)) == LUA_TROTABLE) #define lua_isanytable(L,n) (lua_istable(L,n) || lua_isrotable(L,n)) -#define lua_islightuserdata(L,n) (lua_type(L, (n) == LUA_TLIGHTUSERDATA) +#define lua_islightuserdata(L,n) (lua_type(L, (n)) == LUA_TLIGHTUSERDATA) #define lua_isnil(L,n) (lua_type(L, (n)) == LUA_TNIL) #define lua_isboolean(L,n) (lua_type(L, (n)) == LUA_TBOOLEAN) #define lua_isthread(L,n) (lua_type(L, (n)) == LUA_TTHREAD) diff --git a/app/modules/http.c b/app/modules/http.c index fddc902a83..7b2ff11590 100644 --- a/app/modules/http.c +++ b/app/modules/http.c @@ -3,6 +3,7 @@ * vowstar@gmail.com * 2015-12-29 *******************************************************************************/ +#include #include #include "module.h" #include "lauxlib.h" diff --git a/app/platform/u8x8_nodemcu_hal.c b/app/platform/u8x8_nodemcu_hal.c index 82a5a81b12..11000195db 100644 --- a/app/platform/u8x8_nodemcu_hal.c +++ b/app/platform/u8x8_nodemcu_hal.c @@ -9,6 +9,7 @@ #include #include "platform.h" +#include "user_interface.h" #define U8X8_USE_PINS #define U8X8_WITH_USER_PTR diff --git a/app/platform/ucg_nodemcu_hal.c b/app/platform/ucg_nodemcu_hal.c index a72990f6bd..b54a133e70 100644 --- a/app/platform/ucg_nodemcu_hal.c +++ b/app/platform/ucg_nodemcu_hal.c @@ -8,6 +8,7 @@ #include #include "platform.h" +#include "user_interface.h" #define USE_PIN_LIST #include "ucg_nodemcu_hal.h" From 32ad759409cbaaf53d3f51fc2f6573634eaadbc9 Mon Sep 17 00:00:00 2001 From: Philip Gladstone Date: Tue, 10 Sep 2019 06:47:35 -0400 Subject: [PATCH 06/27] Add streaming support for hx711 device (#2793) --- app/modules/hx711.c | 317 ++++++++++++++++++++++++++++++++++++++---- docs/modules/hx711.md | 60 +++++++- 2 files changed, 345 insertions(+), 32 deletions(-) diff --git a/app/modules/hx711.c b/app/modules/hx711.c index 61198e33a1..cd871504c4 100644 --- a/app/modules/hx711.c +++ b/app/modules/hx711.c @@ -3,65 +3,313 @@ #include "module.h" #include "lauxlib.h" +#include "lmem.h" #include "platform.h" #include #include #include "user_interface.h" static uint8_t data_pin; static uint8_t clk_pin; +// The fields below are after the pin_num conversion +static uint8_t pin_data_pin; +static uint8_t pin_clk_pin; + +#ifdef GPIO_INTERRUPT_ENABLE +static task_handle_t tasknumber; + +// HX711_STATUS can be defined to enable the hx711.status() function to get debug info +#undef HX711_STATUS +#define BUFFERS 2 + +typedef struct { + char *buf[BUFFERS]; + uint32_t dropped[BUFFERS]; + uint32_t timestamp[BUFFERS]; + uint32_t interrupts; + uint32_t hx711_interrupts; + uint16_t buflen; + uint16_t used; + uint32_t nobuffer; + uint8_t active; // slot of the active buffer + uint8_t freed; // slot of the most recently freed buffer + uint8_t mode; + uint8_t dropping; // is non zero when there is no space + int cb_ref; +} CONTROL; + +static CONTROL *control; +#endif /*Lua: hx711.init(clk_pin,data_pin)*/ static int hx711_init(lua_State* L) { - clk_pin = luaL_checkinteger(L,1); - data_pin = luaL_checkinteger(L,2); + clk_pin = luaL_checkint(L,1); + data_pin = luaL_checkint(L,2); MOD_CHECK_ID( gpio, clk_pin ); MOD_CHECK_ID( gpio, data_pin ); platform_gpio_mode(clk_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); platform_gpio_write(clk_pin,1);//put chip to sleep. + + pin_data_pin = pin_num[data_pin]; + pin_clk_pin = pin_num[clk_pin]; return 0; } +static int32_t ICACHE_RAM_ATTR read_sample(char mode) { + int i; + int32_t data = 0; + + for (i = 0; i < 24 ; i++){ //clock in the 24 bits + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + data = data << 1; + if (GPIO_REG_READ(GPIO_IN_ADDRESS) & (1 << pin_data_pin)) { + data = i == 0 ? -1 : data | 1; //signextend the first bit + } + } + //add 25th-27th clock pulse to prevent protocol error + for (i = 0; i <= mode; i++) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + } + + return data; +} + +#ifdef GPIO_INTERRUPT_ENABLE +static void ICACHE_RAM_ATTR hx711_data_available() { + if (!control) { + return; + } + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + if (bits & (1 << pin_data_pin)) { + return; // not ready + } + + // Read a sample + int32_t data = read_sample(control->mode); + + if (control->dropping) { + if (control->active == control->freed) { + // still can't advance + control->nobuffer++; + return; + } + // Advance + control->active = (1 + control->active) % BUFFERS; + control->dropping = 0; + } + + // insert into the active buffer + char *dest = control->buf[control->active] + control->used; + *dest++ = data; + *dest++ = data >> 8; + *dest++ = data >> 16; + + control->used += 3; + if (control->used == control->buflen) { + control->used = 0; + control->timestamp[control->active] = system_get_time(); + control->dropped[control->active] = control->nobuffer; + control->nobuffer = 0; + // post task + task_post_medium(tasknumber, control->active); + + uint8_t next_active = (1 + control->active) % BUFFERS; + + if (control->active == control->freed) { + // We can't advance to the buffer + control->dropping = 1; + } else { + // flip to other buffer + control->active = next_active; + } + } +} + +static uint32_t ICACHE_RAM_ATTR hx711_interrupt(uint32_t ret_gpio_status) +{ + // This function really is running at interrupt level with everything + // else masked off. It should take as little time as necessary. + // + // + + // This gets the set of pins which have changed status + uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + + int pin_mask = 1 << pin_data_pin; + int i; + + control->interrupts++; + + if (gpio_status & pin_mask) { + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + control->hx711_interrupts++; + if (!(bits & pin_mask)) { + // is now ready to read + hx711_data_available(); + } + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & pin_mask); + } + + return gpio_status & ~pin_mask; +} + +// Lua: hx711.start( mode, samples, callback ) +static int hx711_start( lua_State* L ) +{ + uint32_t mode = luaL_checkint( L, 1 ); + uint32_t samples = luaL_checkint( L, 2 ); + + if (mode > 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); + } + + if (!samples || samples > 400) { + return luaL_argerror( L, 2, "Samples value out of range (1-400)" ); + } + + if (control) { + return luaL_error( L, "Already running" ); + } + + int buflen = 3 * samples; + + control = (CONTROL *) luaM_malloc(L, sizeof(CONTROL) + BUFFERS * buflen); + if (!control) { + return luaL_error( L, "Failed to allocate memory" ); + } + + int cb_ref; + + if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) { + lua_pushvalue(L, 3); // copy argument (func) to the top of stack + cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + luaM_free(L, control); + control = NULL; + return luaL_argerror( L, 3, "Not a callback function" ); + } + + memset(control, 0, sizeof(*control)); + control->buf[0] = (char *) (control + 1); + control->buflen = buflen; + int i; + + for (i = 1; i < BUFFERS; i++) { + control->buf[i] = control->buf[i - 1] + buflen; + } + control->mode = mode; + control->cb_ref = cb_ref; + control->freed = BUFFERS - 1; + + // configure data_pin as interrupt input + platform_gpio_register_intr_hook(1 << pin_data_pin, hx711_interrupt); + platform_gpio_mode(data_pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT); + platform_gpio_intr_init(data_pin, GPIO_PIN_INTR_NEGEDGE); + + + // Wake up chip + platform_gpio_write(clk_pin, 0); + + return 0; +} + +// Lua: hx711.stop( ) +static int hx711_stop( lua_State* L ) +{ + if (control) { + platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); + CONTROL *to_free = control; + control = NULL; + luaL_unref(L, LUA_REGISTRYINDEX, to_free->cb_ref); + luaM_free(L, to_free); + } + + return 0; +} + +static int hx711_status( lua_State* L ) +{ + if (control) { + lua_pushlstring(L, (char *) control, sizeof(*control)); + return 1; + } + + return 0; +} + +static void hx711_task(os_param_t param, uint8_t prio) +{ + (void) prio; + if (!control) { + return; + } + + lua_State *L = lua_getstate(); + + if (control->cb_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, control->cb_ref); + + lua_pushlstring(L, control->buf[param], control->buflen); + lua_pushinteger(L, control->timestamp[param]); + lua_pushinteger(L, control->dropped[param]); + + control->freed = param; + + lua_call(L, 3, 0); + } +} +#endif + #define HX711_MAX_WAIT 1000000 /*will only read chA@128gain*/ /*Lua: result = hx711.read()*/ -static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { - uint32_t i; - int32_t data = 0; +static int hx711_read(lua_State* L) { + int j; //TODO: double check init has happened first. + // - //wakeup hx711 - platform_gpio_write(clk_pin,0); + uint32_t mode = luaL_optinteger(L, 1, 0); - //wait for data ready. or time out. - //TODO: set pin inturrupt and come back to it. This may take up to 1/10 sec - // or maybe just make an async version too and have both available. - system_soft_wdt_feed(); //clear WDT... this may take a while. - for (i = 0; i 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); } - //Handle timeout error - if (i>=HX711_MAX_WAIT) { - return luaL_error( L, "ADC timeout!", ( unsigned )0 ); +#ifdef GPIO_INTERRUPT_ENABLE + if (control) { + hx711_stop(L); } +#endif + + //wakeup hx711 + platform_gpio_write(clk_pin, 0); + + int32_t data; - for (i = 0; i<24 ; i++){ //clock in the 24 bits - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - data = data<<1; - if (platform_gpio_read(data_pin)==1) { - data = i==0 ? -1 : data|1; //signextend the first bit + // read two samples if mode > 0. We discard the first read and return the + // second value. + for (j = (mode ? 1 : 0); j >= 0; j--) { + uint32_t i; + + //wait for data ready. or time out. + system_soft_wdt_feed(); //clear WDT... this may take a while. + for (i = 0; i= HX711_MAX_WAIT) { + return luaL_error( L, "ADC timeout!"); } + + data = read_sample(mode); } - //add 25th clock pulse to prevent protocol error (probably not needed - // since we'll go to sleep immediately after and reset on wakeup.) - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - //sleep - platform_gpio_write(clk_pin,1); - lua_pushinteger( L, data ); + + //sleep -- unfortunately, this resets the mode to 0 + platform_gpio_write(clk_pin, 1); + lua_pushinteger(L, data); return 1; } @@ -69,11 +317,20 @@ static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { LROT_BEGIN(hx711) LROT_FUNCENTRY( init, hx711_init ) LROT_FUNCENTRY( read, hx711_read ) +#ifdef GPIO_INTERRUPT_ENABLE + LROT_FUNCENTRY( start, hx711_start ) +#ifdef HX711_STATUS + LROT_FUNCENTRY( status, hx711_status ) +#endif + LROT_FUNCENTRY( stop, hx711_stop ) +#endif LROT_END( hx711, NULL, 0 ) int luaopen_hx711(lua_State *L) { - // TODO: Make sure that the GPIO system is initialized +#ifdef GPIO_INTERRUPT_ENABLE + tasknumber = task_get_id(hx711_task); +#endif return 0; } diff --git a/docs/modules/hx711.md b/docs/modules/hx711.md index 0eaa3cff19..1095ebae2d 100644 --- a/docs/modules/hx711.md +++ b/docs/modules/hx711.md @@ -2,8 +2,11 @@ | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | | 2015-10-09 | [Chris Takahashi](https://github.com/christakahashi) | [Chris Takahashi](https://github.com/christakahashi) | [hx711.c](../../app/modules/hx711.c)| +| 2019-04-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) -This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. Currently only channel A at 128x gain is supported. +This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. The standard Chinese sources have [cheap HX711 boards](https://www.aliexpress.com/wholesale?SearchText=hx711+module) for around $1. + +This can be used for single shot reads, or repetitive reads. Note: To save ROM image space, this module is not compiled into the firmware by default. @@ -35,11 +38,13 @@ Read digital loadcell ADC value. `hx711.read(mode)` #### Parameters -`mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. Currently only channel A @ 128 gain is supported. +- `mode` ADC mode. This parameter specifies which input and the gain to apply to that input. Reading in mode 1 or 2 takes longer than reading in mode 0. |mode | channel | gain | |-----|---------|------| | 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | #### Returns a number (24 bit signed ADC value extended to the machine int size) @@ -49,3 +54,54 @@ a number (24 bit signed ADC value extended to the machine int size) -- Read ch A with 128 gain. raw_data = hx711.read(0) ``` + +## hx711.start() + +Starts to read multiple samples from the ADC. + +#### Syntax +`hx711.start(mode, samples, callback)` + +#### Parameters +- `mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. +- `samples` The number of samples before the callback is invoked. The length of time depends on the chip's sampling rate. +- `callback` The callback is invoked with three arguments (see below). + +|mode | channel | gain | +|-----|---------|------| +| 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | + +#### Returns +nothing + +#### Callback +This is invoked every time `samples` samples are read from the HX711. The arguments are: + +- A string which contains `samples` packed 24 bit values. This can be unpacked with the `struct` module (using the "i3" format). +- The time in microseconds of the reception of the last sample in the buffer. +- The number of samples dropped before the start of this buffer (after the end of the previous buffer). + +#### Notes +This api only is built if GPIO_INTERRUPT_ENABLE and GPIO_INTERRUPT_HOOK_ENABLE are defined in the +`user_config.h`. This is the default. + +Also, do not try and mix calls to `start` and calls to `read`. Any calls to `read` will implicitly call `stop` first. + +#### Example +```lua +-- Read ch A with 128 gain. +hx711.start(0, 2, function(s, t, d) local r1, r2, _ = struct.unpack("i3 i3", s) print(r1, r2) end) +``` + +## hx711.stop() + +Stops a previously started set of reads. Any data in buffers is lost. No more callbacks will be invoked. + +#### Syntax +`hx711.stop()` + +#### Returns +nothing + From 4c3d45e41252dafea23b2475c2fc0114248b5c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Fri, 13 Sep 2019 13:47:34 +0200 Subject: [PATCH 07/27] Revert "Add streaming support for hx711 device (#2793)" (#2914) This reverts commit 32ad759409cbaaf53d3f51fc2f6573634eaadbc9. --- app/modules/hx711.c | 317 ++++-------------------------------------- docs/modules/hx711.md | 60 +------- 2 files changed, 32 insertions(+), 345 deletions(-) diff --git a/app/modules/hx711.c b/app/modules/hx711.c index cd871504c4..61198e33a1 100644 --- a/app/modules/hx711.c +++ b/app/modules/hx711.c @@ -3,313 +3,65 @@ #include "module.h" #include "lauxlib.h" -#include "lmem.h" #include "platform.h" #include #include #include "user_interface.h" static uint8_t data_pin; static uint8_t clk_pin; -// The fields below are after the pin_num conversion -static uint8_t pin_data_pin; -static uint8_t pin_clk_pin; - -#ifdef GPIO_INTERRUPT_ENABLE -static task_handle_t tasknumber; - -// HX711_STATUS can be defined to enable the hx711.status() function to get debug info -#undef HX711_STATUS -#define BUFFERS 2 - -typedef struct { - char *buf[BUFFERS]; - uint32_t dropped[BUFFERS]; - uint32_t timestamp[BUFFERS]; - uint32_t interrupts; - uint32_t hx711_interrupts; - uint16_t buflen; - uint16_t used; - uint32_t nobuffer; - uint8_t active; // slot of the active buffer - uint8_t freed; // slot of the most recently freed buffer - uint8_t mode; - uint8_t dropping; // is non zero when there is no space - int cb_ref; -} CONTROL; - -static CONTROL *control; -#endif /*Lua: hx711.init(clk_pin,data_pin)*/ static int hx711_init(lua_State* L) { - clk_pin = luaL_checkint(L,1); - data_pin = luaL_checkint(L,2); + clk_pin = luaL_checkinteger(L,1); + data_pin = luaL_checkinteger(L,2); MOD_CHECK_ID( gpio, clk_pin ); MOD_CHECK_ID( gpio, data_pin ); platform_gpio_mode(clk_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); platform_gpio_write(clk_pin,1);//put chip to sleep. - - pin_data_pin = pin_num[data_pin]; - pin_clk_pin = pin_num[clk_pin]; return 0; } -static int32_t ICACHE_RAM_ATTR read_sample(char mode) { - int i; - int32_t data = 0; - - for (i = 0; i < 24 ; i++){ //clock in the 24 bits - GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); - GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); - data = data << 1; - if (GPIO_REG_READ(GPIO_IN_ADDRESS) & (1 << pin_data_pin)) { - data = i == 0 ? -1 : data | 1; //signextend the first bit - } - } - //add 25th-27th clock pulse to prevent protocol error - for (i = 0; i <= mode; i++) { - GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); - GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); - } - - return data; -} - -#ifdef GPIO_INTERRUPT_ENABLE -static void ICACHE_RAM_ATTR hx711_data_available() { - if (!control) { - return; - } - uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); - if (bits & (1 << pin_data_pin)) { - return; // not ready - } - - // Read a sample - int32_t data = read_sample(control->mode); - - if (control->dropping) { - if (control->active == control->freed) { - // still can't advance - control->nobuffer++; - return; - } - // Advance - control->active = (1 + control->active) % BUFFERS; - control->dropping = 0; - } - - // insert into the active buffer - char *dest = control->buf[control->active] + control->used; - *dest++ = data; - *dest++ = data >> 8; - *dest++ = data >> 16; - - control->used += 3; - if (control->used == control->buflen) { - control->used = 0; - control->timestamp[control->active] = system_get_time(); - control->dropped[control->active] = control->nobuffer; - control->nobuffer = 0; - // post task - task_post_medium(tasknumber, control->active); - - uint8_t next_active = (1 + control->active) % BUFFERS; - - if (control->active == control->freed) { - // We can't advance to the buffer - control->dropping = 1; - } else { - // flip to other buffer - control->active = next_active; - } - } -} - -static uint32_t ICACHE_RAM_ATTR hx711_interrupt(uint32_t ret_gpio_status) -{ - // This function really is running at interrupt level with everything - // else masked off. It should take as little time as necessary. - // - // - - // This gets the set of pins which have changed status - uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); - - int pin_mask = 1 << pin_data_pin; - int i; - - control->interrupts++; - - if (gpio_status & pin_mask) { - uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); - control->hx711_interrupts++; - if (!(bits & pin_mask)) { - // is now ready to read - hx711_data_available(); - } - GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & pin_mask); - } - - return gpio_status & ~pin_mask; -} - -// Lua: hx711.start( mode, samples, callback ) -static int hx711_start( lua_State* L ) -{ - uint32_t mode = luaL_checkint( L, 1 ); - uint32_t samples = luaL_checkint( L, 2 ); - - if (mode > 2) { - return luaL_argerror( L, 1, "Mode value out of range" ); - } - - if (!samples || samples > 400) { - return luaL_argerror( L, 2, "Samples value out of range (1-400)" ); - } - - if (control) { - return luaL_error( L, "Already running" ); - } - - int buflen = 3 * samples; - - control = (CONTROL *) luaM_malloc(L, sizeof(CONTROL) + BUFFERS * buflen); - if (!control) { - return luaL_error( L, "Failed to allocate memory" ); - } - - int cb_ref; - - if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) { - lua_pushvalue(L, 3); // copy argument (func) to the top of stack - cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); - } else { - luaM_free(L, control); - control = NULL; - return luaL_argerror( L, 3, "Not a callback function" ); - } - - memset(control, 0, sizeof(*control)); - control->buf[0] = (char *) (control + 1); - control->buflen = buflen; - int i; - - for (i = 1; i < BUFFERS; i++) { - control->buf[i] = control->buf[i - 1] + buflen; - } - control->mode = mode; - control->cb_ref = cb_ref; - control->freed = BUFFERS - 1; - - // configure data_pin as interrupt input - platform_gpio_register_intr_hook(1 << pin_data_pin, hx711_interrupt); - platform_gpio_mode(data_pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT); - platform_gpio_intr_init(data_pin, GPIO_PIN_INTR_NEGEDGE); - - - // Wake up chip - platform_gpio_write(clk_pin, 0); - - return 0; -} - -// Lua: hx711.stop( ) -static int hx711_stop( lua_State* L ) -{ - if (control) { - platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); - CONTROL *to_free = control; - control = NULL; - luaL_unref(L, LUA_REGISTRYINDEX, to_free->cb_ref); - luaM_free(L, to_free); - } - - return 0; -} - -static int hx711_status( lua_State* L ) -{ - if (control) { - lua_pushlstring(L, (char *) control, sizeof(*control)); - return 1; - } - - return 0; -} - -static void hx711_task(os_param_t param, uint8_t prio) -{ - (void) prio; - if (!control) { - return; - } - - lua_State *L = lua_getstate(); - - if (control->cb_ref != LUA_NOREF) { - lua_rawgeti(L, LUA_REGISTRYINDEX, control->cb_ref); - - lua_pushlstring(L, control->buf[param], control->buflen); - lua_pushinteger(L, control->timestamp[param]); - lua_pushinteger(L, control->dropped[param]); - - control->freed = param; - - lua_call(L, 3, 0); - } -} -#endif - #define HX711_MAX_WAIT 1000000 /*will only read chA@128gain*/ /*Lua: result = hx711.read()*/ -static int hx711_read(lua_State* L) { - int j; +static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { + uint32_t i; + int32_t data = 0; //TODO: double check init has happened first. - // - uint32_t mode = luaL_optinteger(L, 1, 0); + //wakeup hx711 + platform_gpio_write(clk_pin,0); - if (mode > 2) { - return luaL_argerror( L, 1, "Mode value out of range" ); + //wait for data ready. or time out. + //TODO: set pin inturrupt and come back to it. This may take up to 1/10 sec + // or maybe just make an async version too and have both available. + system_soft_wdt_feed(); //clear WDT... this may take a while. + for (i = 0; i=HX711_MAX_WAIT) { + return luaL_error( L, "ADC timeout!", ( unsigned )0 ); } -#endif - - //wakeup hx711 - platform_gpio_write(clk_pin, 0); - - int32_t data; - // read two samples if mode > 0. We discard the first read and return the - // second value. - for (j = (mode ? 1 : 0); j >= 0; j--) { - uint32_t i; - - //wait for data ready. or time out. - system_soft_wdt_feed(); //clear WDT... this may take a while. - for (i = 0; i= HX711_MAX_WAIT) { - return luaL_error( L, "ADC timeout!"); + for (i = 0; i<24 ; i++){ //clock in the 24 bits + platform_gpio_write(clk_pin,1); + platform_gpio_write(clk_pin,0); + data = data<<1; + if (platform_gpio_read(data_pin)==1) { + data = i==0 ? -1 : data|1; //signextend the first bit } - - data = read_sample(mode); } - - //sleep -- unfortunately, this resets the mode to 0 - platform_gpio_write(clk_pin, 1); - lua_pushinteger(L, data); + //add 25th clock pulse to prevent protocol error (probably not needed + // since we'll go to sleep immediately after and reset on wakeup.) + platform_gpio_write(clk_pin,1); + platform_gpio_write(clk_pin,0); + //sleep + platform_gpio_write(clk_pin,1); + lua_pushinteger( L, data ); return 1; } @@ -317,20 +69,11 @@ static int hx711_read(lua_State* L) { LROT_BEGIN(hx711) LROT_FUNCENTRY( init, hx711_init ) LROT_FUNCENTRY( read, hx711_read ) -#ifdef GPIO_INTERRUPT_ENABLE - LROT_FUNCENTRY( start, hx711_start ) -#ifdef HX711_STATUS - LROT_FUNCENTRY( status, hx711_status ) -#endif - LROT_FUNCENTRY( stop, hx711_stop ) -#endif LROT_END( hx711, NULL, 0 ) int luaopen_hx711(lua_State *L) { -#ifdef GPIO_INTERRUPT_ENABLE - tasknumber = task_get_id(hx711_task); -#endif + // TODO: Make sure that the GPIO system is initialized return 0; } diff --git a/docs/modules/hx711.md b/docs/modules/hx711.md index 1095ebae2d..0eaa3cff19 100644 --- a/docs/modules/hx711.md +++ b/docs/modules/hx711.md @@ -2,11 +2,8 @@ | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | | 2015-10-09 | [Chris Takahashi](https://github.com/christakahashi) | [Chris Takahashi](https://github.com/christakahashi) | [hx711.c](../../app/modules/hx711.c)| -| 2019-04-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) -This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. The standard Chinese sources have [cheap HX711 boards](https://www.aliexpress.com/wholesale?SearchText=hx711+module) for around $1. - -This can be used for single shot reads, or repetitive reads. +This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. Currently only channel A at 128x gain is supported. Note: To save ROM image space, this module is not compiled into the firmware by default. @@ -38,13 +35,11 @@ Read digital loadcell ADC value. `hx711.read(mode)` #### Parameters -- `mode` ADC mode. This parameter specifies which input and the gain to apply to that input. Reading in mode 1 or 2 takes longer than reading in mode 0. +`mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. Currently only channel A @ 128 gain is supported. |mode | channel | gain | |-----|---------|------| | 0 | A | 128 | -| 1 | B | 32 | -| 2 | A | 64 | #### Returns a number (24 bit signed ADC value extended to the machine int size) @@ -54,54 +49,3 @@ a number (24 bit signed ADC value extended to the machine int size) -- Read ch A with 128 gain. raw_data = hx711.read(0) ``` - -## hx711.start() - -Starts to read multiple samples from the ADC. - -#### Syntax -`hx711.start(mode, samples, callback)` - -#### Parameters -- `mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. -- `samples` The number of samples before the callback is invoked. The length of time depends on the chip's sampling rate. -- `callback` The callback is invoked with three arguments (see below). - -|mode | channel | gain | -|-----|---------|------| -| 0 | A | 128 | -| 1 | B | 32 | -| 2 | A | 64 | - -#### Returns -nothing - -#### Callback -This is invoked every time `samples` samples are read from the HX711. The arguments are: - -- A string which contains `samples` packed 24 bit values. This can be unpacked with the `struct` module (using the "i3" format). -- The time in microseconds of the reception of the last sample in the buffer. -- The number of samples dropped before the start of this buffer (after the end of the previous buffer). - -#### Notes -This api only is built if GPIO_INTERRUPT_ENABLE and GPIO_INTERRUPT_HOOK_ENABLE are defined in the -`user_config.h`. This is the default. - -Also, do not try and mix calls to `start` and calls to `read`. Any calls to `read` will implicitly call `stop` first. - -#### Example -```lua --- Read ch A with 128 gain. -hx711.start(0, 2, function(s, t, d) local r1, r2, _ = struct.unpack("i3 i3", s) print(r1, r2) end) -``` - -## hx711.stop() - -Stops a previously started set of reads. Any data in buffers is lost. No more callbacks will be invoked. - -#### Syntax -`hx711.stop()` - -#### Returns -nothing - From 81e213ace0144d40a103ed6aeed71b61f251e24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Sat, 21 Sep 2019 17:24:48 +0200 Subject: [PATCH 08/27] Remove superfluous module def Fixes #2920 --- app/include/user_config.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/include/user_config.h b/app/include/user_config.h index d0b923b51c..5734f3be29 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -134,10 +134,6 @@ // Enable creation on the wifi.eventmon.reason table #define WIFI_EVENT_MONITOR_DISCONNECT_REASON_LIST_ENABLE -// Enable use of the WiFi.monitor sub-module -//#define LUA_USE_MODULES_WIFI_MONITOR - - // Whilst the DNS client details can be configured through the WiFi API, // the defaults can be exposed temporarily during start-up. The following // WIFI_STA options allow you to configure this in the firmware. If the From 04287acbd4f13a07189ce6b971654ca7c284ec79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Sun, 29 Sep 2019 20:40:56 +0200 Subject: [PATCH 09/27] Fix invalid smartconfig include Fixes #2928, #2923 --- app/modules/wifi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/modules/wifi.c b/app/modules/wifi.c index 7d5766a575..27ec988e16 100644 --- a/app/modules/wifi.c +++ b/app/modules/wifi.c @@ -17,7 +17,7 @@ #ifdef WIFI_SMART_ENABLE #include "smart/smart.h" -#include "smart/smartconfig.h" +#include "smartconfig.h" static int wifi_smart_succeed = LUA_NOREF; #endif From 043046d2ab06fdefdae2a097c7c0f01cdb8da2fb Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Mon, 30 Sep 2019 13:41:51 +0100 Subject: [PATCH 10/27] Deprecate crypto.to{Hex,Base64} (#2929) The internal implementation already preferentially forwards to the encoder module, so we should just remove these functions as they confuse people into thinking that we don't have their inverses (see the feature request https://github.com/nodemcu/nodemcu-firmware/issues/2907). Update the docs to refer to the encoder version and add deprecation warnings to the runtime implementations. --- app/modules/crypto.c | 6 ++++++ docs/modules/crypto.md | 32 ++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/app/modules/crypto.c b/app/modules/crypto.c index 3deabdaa1d..81443181ab 100644 --- a/app/modules/crypto.c +++ b/app/modules/crypto.c @@ -61,9 +61,11 @@ static int call_encoder( lua_State* L, const char *function ) { } static int crypto_base64_encode (lua_State* L) { + platform_print_deprecation_note("crypto.toBase64", "in the next version"); return call_encoder(L, "toBase64"); } static int crypto_hex_encode (lua_State* L) { + platform_print_deprecation_note("crypto.toHex", "in the next version"); return call_encoder(L, "toHex"); } #else @@ -79,6 +81,8 @@ static int crypto_base64_encode( lua_State* L ) const char* msg = luaL_checklstring(L, 1, &len); luaL_Buffer out; + platform_print_deprecation_note("crypto.toBase64", "in the next version"); + luaL_buffinit(L, &out); for (i = 0; i < len; i += 3) { int a = msg[i]; @@ -104,6 +108,8 @@ static int crypto_hex_encode( lua_State* L) const char* msg = luaL_checklstring(L, 1, &len); luaL_Buffer out; + platform_print_deprecation_note("crypto.toHex", "in the next version"); + luaL_buffinit(L, &out); for (i = 0; i < len; i++) { luaL_addchar(&out, crypto_hexbytes[msg[i] >> 4]); diff --git a/docs/modules/crypto.md b/docs/modules/crypto.md index 97f9dec296..75bec7e357 100644 --- a/docs/modules/crypto.md +++ b/docs/modules/crypto.md @@ -33,7 +33,7 @@ The encrypted data as a binary string. For AES this is always a multiple of 16 b #### Example ```lua -print(crypto.toHex(crypto.encrypt("AES-ECB", "1234567890abcdef", "Hi, I'm secret!"))) +print(encoder.toHex(crypto.encrypt("AES-ECB", "1234567890abcdef", "Hi, I'm secret!"))) ``` #### See also @@ -62,7 +62,7 @@ Note that the decrypted string may contain extra zero-bytes of padding at the en ```lua key = "1234567890abcdef" cipher = crypto.encrypt("AES-ECB", key, "Hi, I'm secret!") -print(crypto.toHex(cipher)) +print(encoder.toHex(cipher)) print(crypto.decrypt("AES-ECB", key, cipher)) ``` @@ -82,11 +82,11 @@ Compute a cryptographic hash of a a file. - `filename` the path to the file to hash #### Returns -A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex ). +A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`encoder.toHex()`](encoder.md#encodertohex ). #### Example ```lua -print(crypto.toHex(crypto.fhash("sha1","myfile.lua"))) +print(encoder.toHex(crypto.fhash("sha1","myfile.lua"))) ``` ## crypto.hash() @@ -101,11 +101,11 @@ Compute a cryptographic hash of a Lua string. `str` string to hash contents of #### Returns -A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`crypto.toHex()`](#cryptotohex ). +A binary string containing the message digest. To obtain the textual version (ASCII hex characters), please use [`encoder.toHex()`](encoder.md#encodertohex). #### Example ```lua -print(crypto.toHex(crypto.hash("sha1","abc"))) +print(encoder.toHex(crypto.hash("sha1","abc"))) ``` ## crypto.new_hash() @@ -127,7 +127,7 @@ hashobj = crypto.new_hash("SHA1") hashobj:update("FirstString") hashobj:update("SecondString") digest = hashobj:finalize() -print(crypto.toHex(digest)) +print(encoder.toHex(digest)) ``` ## crypto.hmac() @@ -143,11 +143,11 @@ Compute a [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication - `key` key to use for signing, may be a binary string #### Returns -A binary string containing the HMAC signature. Use [`crypto.toHex()`](#cryptotohex) to obtain the textual version. +A binary string containing the HMAC signature. Use [`encoder.toHex()`](encoder.md#encodertohex) to obtain the textual version. #### Example ```lua -print(crypto.toHex(crypto.hmac("sha1","abc","mysecret"))) +print(encoder.toHex(crypto.hmac("sha1","abc","mysecret"))) ``` ## crypto.new_hmac() @@ -170,7 +170,7 @@ hmacobj = crypto.new_hmac("SHA1", "s3kr3t") hmacobj:update("FirstString") hmacobj:update("SecondString") digest = hmacobj:finalize() -print(crypto.toHex(digest)) +print(encoder.toHex(digest)) ``` @@ -186,17 +186,21 @@ Applies an XOR mask to a Lua string. Note that this is not a proper cryptographi - `mask` the mask to apply, repeated if shorter than the message #### Returns -The masked message, as a binary string. Use [`crypto.toHex()`](#cryptotohex) to get a textual representation of it. +The masked message, as a binary string. Use [`encoder.toHex()`](encoder.md#encodertohex) to get a textual representation of it. #### Example ```lua -print(crypto.toHex(crypto.mask("some message to obscure","X0Y7"))) +print(encoder.toHex(crypto.mask("some message to obscure","X0Y7"))) ``` ## crypto.toBase64() Provides a Base64 representation of a (binary) Lua string. +!!! warning + + This function is deprecated; please use instead [`encoder.toBase64()`](encoder.md#encodertobase64) + #### Syntax `b64 = crypto.toBase64(binary)` @@ -215,6 +219,10 @@ print(crypto.toBase64(crypto.hash("sha1","abc"))) Provides an ASCII hex representation of a (binary) Lua string. Each byte in the input string is represented as two hex characters in the output. +!!! warning + + This function is deprecated; please use instead [`encoder.toHex()`](encoder.md#encodertohex) + #### Syntax `hexstr = crypto.toHex(binary)` From f3044e1aff432ca48f6511554b317e0c80207d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Wed, 30 Oct 2019 22:36:06 +0100 Subject: [PATCH 11/27] Fix typos --- docs/modules/node.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/node.md b/docs/modules/node.md index 3c4a98337b..d082c05c1a 100644 --- a/docs/modules/node.md +++ b/docs/modules/node.md @@ -299,9 +299,9 @@ If a `group` is given the return value will be a table containing the following - `git_commit_id` (string) - `git_release` (string) release name +additional commits e.g. "2.0.0-master_20170202 +403" - `git_commit_dts` (string) commit timestamp in an ordering format. e.g. "201908111200" - - `node_verion_major` (number) - - `node_verion_minor` (number) - - `node_verion_revision` (number) + - `node_version_major` (number) + - `node_version_minor` (number) + - `node_version_revision` (number) - for `group` = `"build_config"` - `ssl` (boolean) - `lfs_size` (number) as defined at build time From 0e34f7d3090fa262c90184a9e204b58fc9798583 Mon Sep 17 00:00:00 2001 From: "Matsievskiy S.V" Date: Wed, 13 Nov 2019 23:48:20 +0300 Subject: [PATCH 12/27] fix lfs upload issue --- app/lua/lflash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lua/lflash.c b/app/lua/lflash.c index 221a667062..a3afcf9b46 100644 --- a/app/lua/lflash.c +++ b/app/lua/lflash.c @@ -539,7 +539,7 @@ static int loadLFS (lua_State *L) { flashSetPosition(0); if ((res = uzlib_inflate(get_byte, put_byte, recall_byte, - in->len, &crc, &in->inflate_state)) != UZLIB_OK) { + in->len, &crc, &in->inflate_state)) != UZLIB_DONE) { const char *err[] = {"Data_error during decompression", "Chksum_error during decompression", "Dictionary error during decompression", From e3935debd3a435700aa209bbc6212ef549e839f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Str=C3=B6m?= Date: Thu, 21 Nov 2019 12:38:07 +0100 Subject: [PATCH 13/27] Improve MQTT documentation (#2967) --- docs/modules/mqtt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/mqtt.md b/docs/modules/mqtt.md index 0a01d1bab9..0b9261df80 100644 --- a/docs/modules/mqtt.md +++ b/docs/modules/mqtt.md @@ -157,8 +157,8 @@ end In reality, the connected function should do something useful! -The two callbacks to `:connect()` alias with the "connect" and "offline" -callbacks available through `:on()`. +The first callback to `:connect()` aliases with the "connect" callback available through `:on()` (the last passed callback to either of those are used). +The second (failure) callback is however not the same as the "offline" `:on()` callback. The "offline" callback is only called after an already established connection becomes closed. If the `connect()` call fails to establish a connection, the callback passed to `:connect()` is called and nothing else. Previously, we instructed an application to pass either the *integer* 0 or *integer* 1 for `secure`. Now, this will trigger a deprecation warning; please From 9a2579d97160e981cd084ff483f0eeac8210da37 Mon Sep 17 00:00:00 2001 From: Gregor Hartmann Date: Tue, 26 Nov 2019 12:12:45 +0100 Subject: [PATCH 14/27] Improve httpserver documentation (#2971) --- docs/lua-modules/httpserver.md | 29 ++++++++++++++++++++++------- lua_modules/http/httpserver.lua | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/lua-modules/httpserver.md b/docs/lua-modules/httpserver.md index 0d3139ff1c..64634e78b5 100644 --- a/docs/lua-modules/httpserver.md +++ b/docs/lua-modules/httpserver.md @@ -36,20 +36,35 @@ Callback function has 2 arguments: `req` (request) and `res` (response). The fir object. - `method`: Request method that was used (e.g.`POST` or `GET`) - `url`: Requested URL -- `onheader`: value to setup handler function for HTTP headers like `content-type`. Handler function has 3 parameters: +- `onheader`: assign a function to this value which will be called as soon as HTTP headers like `content-type` are available. + This handler function has 3 parameters: - `self`: `req` object - - `name`: Header name + - `name`: Header name. Will allways be lowercase. - `value`: Header value -- `ondata`: value to setup handler function HTTP data. Handler function has 2 parameters: +- `ondata`: assign a function to this value which will be called as soon as body data is available. + This handler function has 2 parameters: - `self`: `req` object - - `chunk`: Request data + - `chunk`: Request data. If all data is received there will be one last call with data = nil The second object holds functions: -- `send(self, data, [response_code])`: Function to send data to client. `self` is `req` object, `data` is data to send and `response_code` is HTTP response code like `200` or `404` (for example) -- `send_header(self, header_name, header_data)`: Function to send HTTP headers to client. `self` is `req` object, `header_name` is HTTP header name and `header_data` is HTTP header data for client. -- `finish([data])`: Function to finalize connection, optionally sending data. `data` is optional data to send on connection finalizing. +- `send(self, data, [response_code])`: Function to send data to client. + + - `self`: `res` object + - `data`: data to send (may be nil) + - `response_code`: the HTTP response code like `200`(default) or `404` (for example) *NOTE* if there are several calls with response_code given only the first one will be used. Any further codes given will be ignored. + +- `send_header(self, header_name, header_data)`: Function to send HTTP headers to client. This function will not be available after data has been sent. (It will be nil.) + + - `self`: `res` object + - `header_name`: the HTTP header name + - `header_data`: the HTTP header data + +- `finish([data[, response_code]])`: Function to finalize connection, optionally sending data and return code. + + - `data`: optional data to send on connection finalizing + - `response_code`: the HTTP response code like `200`(default) or `404` (for example) *NOTE* if there are several calls with response_code given only the first one will be used. Any further codes given will be ignored. Full example can be found in [http-example.lua](../../lua_modules/http/http-example.lua) diff --git a/lua_modules/http/httpserver.lua b/lua_modules/http/httpserver.lua index ba2696036e..50717e1e96 100644 --- a/lua_modules/http/httpserver.lua +++ b/lua_modules/http/httpserver.lua @@ -116,7 +116,7 @@ do if not req or not req.ondata then return end req:ondata(chunk) -- NB: once length of seen chunks equals Content-Length: - -- onend(conn) is called + -- ondata(conn) is called body_len = body_len + #chunk -- print("-B", #chunk, body_len, cnt_len, node.heap()) if body_len >= cnt_len then From c7d0f836257060dbd1f6655cb066e6ae0d8e4443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Voborsk=C3=BD?= Date: Mon, 9 Dec 2019 13:29:23 +0100 Subject: [PATCH 15/27] Update sensor driver for BME680 to 3.5.9 (#2969) --- app/modules/bme680.c | 131 ++++++++++++---------- app/modules/bme680_defs.h | 227 ++++++++++++++++++++------------------ 2 files changed, 193 insertions(+), 165 deletions(-) diff --git a/app/modules/bme680.c b/app/modules/bme680.c index dcf6b80059..c4a08f6273 100644 --- a/app/modules/bme680.c +++ b/app/modules/bme680.c @@ -68,7 +68,10 @@ static uint8_t r8u(uint8_t reg) { return ret[0]; } -/* This part of code is coming from the original bme680.c driver by Bosch. +// replace 'dev->calib.' with 'bme680_data.' +// replace 'dev->amb_temp' with 'amb_temp' + +/**\mainpage * Copyright (C) 2017 - 2018 Bosch Sensortec GmbH * * Redistribution and use in source and binary forms, with or without @@ -108,20 +111,13 @@ static uint8_t r8u(uint8_t reg) { * other rights of third parties which may result from its use. * No license is granted by implication or otherwise under any patent or * patent rights of the copyright holder. + * + * File bme680.c + * @date 19 Jun 2018 + * @version 3.5.9 + * */ -/**static variables */ -/**Look up table for the possible gas range values */ -uint32_t lookupTable1[16] = { UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), - UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), UINT32_C(2147483647), - UINT32_C(2147483647), UINT32_C(2143188679), UINT32_C(2136746228), UINT32_C(2147483647), UINT32_C(2126008810), - UINT32_C(2147483647), UINT32_C(2147483647) }; -/**Look up table for the possible gas range values */ -uint32_t lookupTable2[16] = { UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), - UINT32_C(255744255), UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), UINT32_C(16016016), UINT32_C( - 8000000), UINT32_C(4000000), UINT32_C(2000000), UINT32_C(1000000), UINT32_C(500000), UINT32_C(250000), - UINT32_C(125000) }; - static uint8_t calc_heater_res(uint16_t temp) { uint8_t heatr_res; @@ -132,9 +128,7 @@ static uint8_t calc_heater_res(uint16_t temp) int32_t var5; int32_t heatr_res_x100; - if (temp < 200) /* Cap temperature */ - temp = 200; - else if (temp > 400) + if (temp > 400) /* Cap temperature */ temp = 400; var1 = (((int32_t) amb_temp * bme680_data.par_gh3) / 1000) * 256; @@ -173,12 +167,12 @@ static int16_t calc_temperature(uint32_t temp_adc) int64_t var3; int16_t calc_temp; - var1 = ((int32_t) temp_adc / 8) - ((int32_t) bme680_data.par_t1 * 2); - var2 = (var1 * (int32_t) bme680_data.par_t2) / 2048; - var3 = ((var1 / 2) * (var1 / 2)) / 4096; - var3 = ((var3) * ((int32_t) bme680_data.par_t3 * 16)) / 16384; + var1 = ((int32_t) temp_adc >> 3) - ((int32_t) bme680_data.par_t1 << 1); + var2 = (var1 * (int32_t) bme680_data.par_t2) >> 11; + var3 = ((var1 >> 1) * (var1 >> 1)) >> 12; + var3 = ((var3) * ((int32_t) bme680_data.par_t3 << 4)) >> 14; bme680_data.t_fine = (int32_t) (var2 + var3); - calc_temp = (int16_t) (((bme680_data.t_fine * 5) + 128) / 256); + calc_temp = (int16_t) (((bme680_data.t_fine * 5) + 128) >> 8); return calc_temp; } @@ -188,27 +182,37 @@ static uint32_t calc_pressure(uint32_t pres_adc) int32_t var1; int32_t var2; int32_t var3; - int32_t calc_pres; - - var1 = (((int32_t) bme680_data.t_fine) / 2) - 64000; - var2 = ((var1 / 4) * (var1 / 4)) / 2048; - var2 = ((var2) * (int32_t) bme680_data.par_p6) / 4; - var2 = var2 + ((var1 * (int32_t) bme680_data.par_p5) * 2); - var2 = (var2 / 4) + ((int32_t) bme680_data.par_p4 * 65536); - var1 = ((var1 / 4) * (var1 / 4)) / 8192; - var1 = (((var1) * ((int32_t) bme680_data.par_p3 * 32)) / 8) + (((int32_t) bme680_data.par_p2 * var1) / 2); - var1 = var1 / 262144; - var1 = ((32768 + var1) * (int32_t) bme680_data.par_p1) / 32768; - calc_pres = (int32_t) (1048576 - pres_adc); - calc_pres = (int32_t) ((calc_pres - (var2 / 4096)) * (3125)); - calc_pres = ((calc_pres / var1) * 2); - var1 = ((int32_t) bme680_data.par_p9 * (int32_t) (((calc_pres / 8) * (calc_pres / 8)) / 8192)) / 4096; - var2 = ((int32_t) (calc_pres / 4) * (int32_t) bme680_data.par_p8) / 8192; - var3 = ((int32_t) (calc_pres / 256) * (int32_t) (calc_pres / 256) * (int32_t) (calc_pres / 256) - * (int32_t) bme680_data.par_p10) / 131072; - calc_pres = (int32_t) (calc_pres) + ((var1 + var2 + var3 + ((int32_t) bme680_data.par_p7 * 128)) / 16); - - return (uint32_t) calc_pres; + int32_t pressure_comp; + + var1 = (((int32_t)bme680_data.t_fine) >> 1) - 64000; + var2 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * + (int32_t)bme680_data.par_p6) >> 2; + var2 = var2 + ((var1 * (int32_t)bme680_data.par_p5) << 1); + var2 = (var2 >> 2) + ((int32_t)bme680_data.par_p4 << 16); + var1 = (((((var1 >> 2) * (var1 >> 2)) >> 13) * + ((int32_t)bme680_data.par_p3 << 5)) >> 3) + + (((int32_t)bme680_data.par_p2 * var1) >> 1); + var1 = var1 >> 18; + var1 = ((32768 + var1) * (int32_t)bme680_data.par_p1) >> 15; + pressure_comp = 1048576 - pres_adc; + pressure_comp = (int32_t)((pressure_comp - (var2 >> 12)) * ((uint32_t)3125)); + if (pressure_comp >= BME680_MAX_OVERFLOW_VAL) + pressure_comp = ((pressure_comp / var1) << 1); + else + pressure_comp = ((pressure_comp << 1) / var1); + var1 = ((int32_t)bme680_data.par_p9 * (int32_t)(((pressure_comp >> 3) * + (pressure_comp >> 3)) >> 13)) >> 12; + var2 = ((int32_t)(pressure_comp >> 2) * + (int32_t)bme680_data.par_p8) >> 13; + var3 = ((int32_t)(pressure_comp >> 8) * (int32_t)(pressure_comp >> 8) * + (int32_t)(pressure_comp >> 8) * + (int32_t)bme680_data.par_p10) >> 17; + + pressure_comp = (int32_t)(pressure_comp) + ((var1 + var2 + var3 + + ((int32_t)bme680_data.par_p7 << 7)) >> 4); + + return (uint32_t)pressure_comp; + } static uint32_t calc_humidity(uint16_t hum_adc) @@ -222,19 +226,19 @@ static uint32_t calc_humidity(uint16_t hum_adc) int32_t temp_scaled; int32_t calc_hum; - temp_scaled = (((int32_t) bme680_data.t_fine * 5) + 128) / 256; + temp_scaled = (((int32_t) bme680_data.t_fine * 5) + 128) >> 8; var1 = (int32_t) (hum_adc - ((int32_t) ((int32_t) bme680_data.par_h1 * 16))) - - (((temp_scaled * (int32_t) bme680_data.par_h3) / ((int32_t) 100)) / 2); + - (((temp_scaled * (int32_t) bme680_data.par_h3) / ((int32_t) 100)) >> 1); var2 = ((int32_t) bme680_data.par_h2 - * (((temp_scaled * (int32_t) bme680_data.par_h4) / ((int32_t) 100)) - + (((temp_scaled * ((temp_scaled * (int32_t) bme680_data.par_h5) / ((int32_t) 100))) / 64) - / ((int32_t) 100)) + (int32_t) (1 * 16384))) / 1024; + * (((temp_scaled * (int32_t) bme680_data.par_h4) / ((int32_t) 100)) + + (((temp_scaled * ((temp_scaled * (int32_t) bme680_data.par_h5) / ((int32_t) 100))) >> 6) + / ((int32_t) 100)) + (int32_t) (1 << 14))) >> 10; var3 = var1 * var2; - var4 = (int32_t) bme680_data.par_h6 * 128; - var4 = ((var4) + ((temp_scaled * (int32_t) bme680_data.par_h7) / ((int32_t) 100))) / 16; - var5 = ((var3 / 16384) * (var3 / 16384)) / 1024; - var6 = (var4 * var5) / 2; - calc_hum = (((var3 + var6) / 1024) * ((int32_t) 1000)) / 4096; + var4 = (int32_t) bme680_data.par_h6 << 7; + var4 = ((var4) + ((temp_scaled * (int32_t) bme680_data.par_h7) / ((int32_t) 100))) >> 4; + var5 = ((var3 >> 14) * (var3 >> 14)) >> 10; + var6 = (var4 * var5) >> 1; + calc_hum = (((var3 + var6) >> 10) * ((int32_t) 1000)) >> 12; if (calc_hum > 100000) /* Cap at 100%rH */ calc_hum = 100000; @@ -244,6 +248,19 @@ static uint32_t calc_humidity(uint16_t hum_adc) return (uint32_t) calc_hum; } + +/**static variables */ + /**Look up table 1 for the possible gas range values */ + uint32_t lookupTable1[16] = { UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2147483647), + UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2130303777), + UINT32_C(2147483647), UINT32_C(2147483647), UINT32_C(2143188679), UINT32_C(2136746228), + UINT32_C(2147483647), UINT32_C(2126008810), UINT32_C(2147483647), UINT32_C(2147483647) }; + /**Look up table 2 for the possible gas range values */ + uint32_t lookupTable2[16] = { UINT32_C(4096000000), UINT32_C(2048000000), UINT32_C(1024000000), UINT32_C(512000000), + UINT32_C(255744255), UINT32_C(127110228), UINT32_C(64000000), UINT32_C(32258064), UINT32_C(16016016), + UINT32_C(8000000), UINT32_C(4000000), UINT32_C(2000000), UINT32_C(1000000), UINT32_C(500000), + UINT32_C(250000), UINT32_C(125000) }; + static uint32_t calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range) { int64_t var1; @@ -251,14 +268,16 @@ static uint32_t calc_gas_resistance(uint16_t gas_res_adc, uint8_t gas_range) int64_t var3; uint32_t calc_gas_res; - var1 = (int64_t) ((1340 + (5 * (int64_t) bme680_data.range_sw_err)) * ((int64_t) lookupTable1[gas_range])) / 65536; - var2 = (((int64_t) ((int64_t) gas_res_adc * 32768) - (int64_t) (16777216)) + var1); - var3 = (((int64_t) lookupTable2[gas_range] * (int64_t) var1) / 512); - calc_gas_res = (uint32_t) ((var3 + ((int64_t) var2 / 2)) / (int64_t) var2); + var1 = (int64_t) ((1340 + (5 * (int64_t) bme680_data.range_sw_err)) * + ((int64_t) lookupTable1[gas_range])) >> 16; + var2 = (((int64_t) ((int64_t) gas_res_adc << 15) - (int64_t) (16777216)) + var1); + var3 = (((int64_t) lookupTable2[gas_range] * (int64_t) var1) >> 9); + calc_gas_res = (uint32_t) ((var3 + ((int64_t) var2 >> 1)) / (int64_t) var2); return calc_gas_res; } + uint16_t calc_dur() { uint32_t tph_dur; /* Calculate in us */ diff --git a/app/modules/bme680_defs.h b/app/modules/bme680_defs.h index 4ef701f6ea..4dfccbebc2 100644 --- a/app/modules/bme680_defs.h +++ b/app/modules/bme680_defs.h @@ -39,78 +39,47 @@ * No license is granted by implication or otherwise under any patent or * patent rights of the copyright holder. * - * @file bme680_defs.h - * @date 5 Jul 2017 - * @version 3.5.1 - * @brief - * + * @file bme680_defs.h + * @date 19 Jun 2018 + * @version 3.5.9 + * @brief Sensor driver for BME680 sensor */ -/*! @file bme680_defs.h - @brief Sensor driver for BME680 sensor */ -/*! - * @defgroup BME680 SENSOR API - * @brief - * @{*/ #ifndef BME680_DEFS_H_ #define BME680_DEFS_H_ -/********************************************************/ -/* header includes */ +/** header includes **/ #ifdef __KERNEL__ #include +#include #else #include +#include #endif -#ifdef __KERNEL__ -#if (LONG_MAX) > 0x7fffffff -#define __have_long64 1 -#elif (LONG_MAX) == 0x7fffffff -#define __have_long32 1 -#endif +/** Common macros **/ -#if !defined(UINT8_C) -#define INT8_C(x) x -#if (INT_MAX) > 0x7f -#define UINT8_C(x) x -#else -#define UINT8_C(x) x##U -#endif +#if !defined(UINT8_C) && !defined(INT8_C) +#define INT8_C(x) S8_C(x) +#define UINT8_C(x) U8_C(x) #endif -#if !defined(UINT16_C) -#define INT16_C(x) x -#if (INT_MAX) > 0x7fff -#define UINT16_C(x) x -#else -#define UINT16_C(x) x##U -#endif +#if !defined(UINT16_C) && !defined(INT16_C) +#define INT16_C(x) S16_C(x) +#define UINT16_C(x) U16_C(x) #endif #if !defined(INT32_C) && !defined(UINT32_C) -#if __have_long32 -#define INT32_C(x) x##L -#define UINT32_C(x) x##UL -#else -#define INT32_C(x) x -#define UINT32_C(x) x##U -#endif +#define INT32_C(x) S32_C(x) +#define UINT32_C(x) U32_C(x) #endif #if !defined(INT64_C) && !defined(UINT64_C) -#if __have_long64 -#define INT64_C(x) x##L -#define UINT64_C(x) x##UL -#else -#define INT64_C(x) x##LL -#define UINT64_C(x) x##ULL -#endif -#endif +#define INT64_C(x) S64_C(x) +#define UINT64_C(x) U64_C(x) #endif -/**@}*/ -/**\name C standard macros */ +/** C standard macros **/ #ifndef NULL #ifdef __cplusplus #define NULL 0 @@ -119,29 +88,35 @@ #endif #endif -/** BME680 General config */ +/** BME680 configuration macros */ +/** Enable or un-comment the macro to provide floating point data output **/ +#ifndef BME680_FLOAT_POINT_COMPENSATION +/* #define BME680_FLOAT_POINT_COMPENSATION **/ +#endif + +/** BME680 General config **/ #define BME680_POLL_PERIOD_MS UINT8_C(10) -/** BME680 I2C addresses */ +/** BME680 I2C addresses **/ #define BME680_I2C_ADDR_PRIMARY UINT8_C(0x76) #define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) -/** BME680 unique chip identifier */ +/** BME680 unique chip identifier **/ #define BME680_CHIP_ID UINT8_C(0x61) -/** BME680 coefficients related defines */ -#define BME680_COEFF_SIZE UINT8_C(0x41) +/** BME680 coefficients related defines **/ +#define BME680_COEFF_SIZE UINT8_C(41) #define BME680_COEFF_ADDR1_LEN UINT8_C(25) #define BME680_COEFF_ADDR2_LEN UINT8_C(16) -/** BME680 field_x related defines */ +/** BME680 field_x related defines **/ #define BME680_FIELD_LENGTH UINT8_C(15) #define BME680_FIELD_ADDR_OFFSET UINT8_C(17) -/** Soft reset command */ +/** Soft reset command **/ #define BME680_SOFT_RESET_CMD UINT8_C(0xb6) -/** Error code definitions */ +/** Error code definitions **/ #define BME680_OK INT8_C(0) /* Errors */ #define BME680_E_NULL_PTR INT8_C(-1) @@ -157,22 +132,22 @@ #define BME680_I_MIN_CORRECTION UINT8_C(1) #define BME680_I_MAX_CORRECTION UINT8_C(2) -/** Register map */ -/** Other coefficient's address */ +/** Register map **/ +/** Other coefficient's address **/ #define BME680_ADDR_RES_HEAT_VAL_ADDR UINT8_C(0x00) #define BME680_ADDR_RES_HEAT_RANGE_ADDR UINT8_C(0x02) #define BME680_ADDR_RANGE_SW_ERR_ADDR UINT8_C(0x04) #define BME680_ADDR_SENS_CONF_START UINT8_C(0x5A) #define BME680_ADDR_GAS_CONF_START UINT8_C(0x64) -/** Field settings */ +/** Field settings **/ #define BME680_FIELD0_ADDR UINT8_C(0x1d) -/** Heater settings */ +/** Heater settings **/ #define BME680_RES_HEAT0_ADDR UINT8_C(0x5a) #define BME680_GAS_WAIT0_ADDR UINT8_C(0x64) -/** Sensor configuration registers */ +/** Sensor configuration registers **/ #define BME680_CONF_HEAT_CTRL_ADDR UINT8_C(0x70) #define BME680_CONF_ODR_RUN_GAS_NBC_ADDR UINT8_C(0x71) #define BME680_CONF_OS_H_ADDR UINT8_C(0x72) @@ -180,25 +155,25 @@ #define BME680_CONF_T_P_MODE_ADDR UINT8_C(0x74) #define BME680_CONF_ODR_FILT_ADDR UINT8_C(0x75) -/** Coefficient's address */ +/** Coefficient's address **/ #define BME680_COEFF_ADDR1 UINT8_C(0x89) #define BME680_COEFF_ADDR2 UINT8_C(0xe1) -/** Chip identifier */ +/** Chip identifier **/ #define BME680_CHIP_ID_ADDR UINT8_C(0xd0) -/** Soft reset register */ +/** Soft reset register **/ #define BME680_SOFT_RESET_ADDR UINT8_C(0xe0) -/** Heater control settings */ +/** Heater control settings **/ #define BME680_ENABLE_HEATER UINT8_C(0x00) #define BME680_DISABLE_HEATER UINT8_C(0x08) -/** Gas measurement settings */ +/** Gas measurement settings **/ #define BME680_DISABLE_GAS_MEAS UINT8_C(0x00) #define BME680_ENABLE_GAS_MEAS UINT8_C(0x01) -/** Over-sampling settings */ +/** Over-sampling settings **/ #define BME680_OS_NONE UINT8_C(0) #define BME680_OS_1X UINT8_C(1) #define BME680_OS_2X UINT8_C(2) @@ -206,7 +181,7 @@ #define BME680_OS_8X UINT8_C(4) #define BME680_OS_16X UINT8_C(5) -/** IIR filter settings */ +/** IIR filter settings **/ #define BME680_FILTER_SIZE_0 UINT8_C(0) #define BME680_FILTER_SIZE_1 UINT8_C(1) #define BME680_FILTER_SIZE_3 UINT8_C(2) @@ -220,28 +195,27 @@ #define BME680_SLEEP_MODE UINT8_C(0) #define BME680_FORCED_MODE UINT8_C(1) -/** Delay related macro declaration */ -#define BME680_RESET_PERIOD UINT32_C(10) +/** Delay related macro declaration **/ +#define BME680_RESET_PERIOD UINT32_C(10) -/** SPI memory page settings */ +/** SPI memory page settings **/ #define BME680_MEM_PAGE0 UINT8_C(0x10) #define BME680_MEM_PAGE1 UINT8_C(0x00) -/** Ambient humidity shift value for compensation */ +/** Ambient humidity shift value for compensation **/ #define BME680_HUM_REG_SHIFT_VAL UINT8_C(4) -/** Run gas enable and disable settings */ +/** Run gas enable and disable settings **/ #define BME680_RUN_GAS_DISABLE UINT8_C(0) #define BME680_RUN_GAS_ENABLE UINT8_C(1) -/** Buffer length macro declaration */ +/** Buffer length macro declaration **/ #define BME680_TMP_BUFFER_LENGTH UINT8_C(40) #define BME680_REG_BUFFER_LENGTH UINT8_C(6) #define BME680_FIELD_DATA_LENGTH UINT8_C(3) #define BME680_GAS_REG_BUF_LENGTH UINT8_C(20) -#define BME680_GAS_HEATER_PROF_LEN_MAX UINT8_C(10) -/** Settings selector */ +/** Settings selector **/ #define BME680_OST_SEL UINT16_C(1) #define BME680_OSP_SEL UINT16_C(2) #define BME680_OSH_SEL UINT16_C(4) @@ -250,13 +224,13 @@ #define BME680_HCNTRL_SEL UINT16_C(32) #define BME680_RUN_GAS_SEL UINT16_C(64) #define BME680_NBCONV_SEL UINT16_C(128) -#define BME680_GAS_SENSOR_SEL UINT16_C(BME680_GAS_MEAS_SEL | BME680_RUN_GAS_SEL | BME680_NBCONV_SEL) +#define BME680_GAS_SENSOR_SEL (BME680_GAS_MEAS_SEL | BME680_RUN_GAS_SEL | BME680_NBCONV_SEL) -/** Number of conversion settings*/ +/** Number of conversion settings **/ #define BME680_NBCONV_MIN UINT8_C(0) #define BME680_NBCONV_MAX UINT8_C(10) -/** Mask definitions */ +/** Mask definitions **/ #define BME680_GAS_MEAS_MSK UINT8_C(0x30) #define BME680_NBCONV_MSK UINT8_C(0X0F) #define BME680_FILTER_MSK UINT8_C(0X1C) @@ -278,14 +252,14 @@ #define BME680_SPI_WR_MSK UINT8_C(0x7f) #define BME680_BIT_H1_DATA_MSK UINT8_C(0x0F) -/** Bit position definitions for sensor settings */ +/** Bit position definitions for sensor settings **/ #define BME680_GAS_MEAS_POS UINT8_C(4) #define BME680_FILTER_POS UINT8_C(2) #define BME680_OST_POS UINT8_C(5) #define BME680_OSP_POS UINT8_C(2) #define BME680_RUN_GAS_POS UINT8_C(4) -/** Array Index to Field data mapping for Calibration Data*/ +/** Array Index to Field data mapping for Calibration Data **/ #define BME680_T2_LSB_REG (1) #define BME680_T2_MSB_REG (2) #define BME680_T3_REG (3) @@ -321,7 +295,7 @@ #define BME680_GH1_REG (37) #define BME680_GH3_REG (38) -/** BME680 register buffer index settings*/ +/** BME680 register buffer index settings **/ #define BME680_REG_FILTER_INDEX UINT8_C(5) #define BME680_REG_TEMP_INDEX UINT8_C(4) #define BME680_REG_PRES_INDEX UINT8_C(4) @@ -330,38 +304,51 @@ #define BME680_REG_RUN_GAS_INDEX UINT8_C(1) #define BME680_REG_HCTRL_INDEX UINT8_C(0) -/** Macro to combine two 8 bit data's to form a 16 bit data */ +/** BME680 pressure calculation macros **/ +/*! This max value is used to provide precedence to multiplication or division + * in pressure compensation equation to achieve least loss of precision and + * avoiding overflows. + * i.e Comparing value, BME680_MAX_OVERFLOW_VAL = INT32_C(1 << 30) + */ +#define BME680_MAX_OVERFLOW_VAL INT32_C(0x40000000) + +/** Macro to combine two 8 bit data's to form a 16 bit data **/ #define BME680_CONCAT_BYTES(msb, lsb) (((uint16_t)msb << 8) | (uint16_t)lsb) -/** Macro to SET and GET BITS of a register */ +/** Macro to SET and GET BITS of a register **/ #define BME680_SET_BITS(reg_data, bitname, data) \ ((reg_data & ~(bitname##_MSK)) | \ ((data << bitname##_POS) & bitname##_MSK)) #define BME680_GET_BITS(reg_data, bitname) ((reg_data & (bitname##_MSK)) >> \ (bitname##_POS)) -/** Macro variant to handle the bitname position if it is zero */ +/** Macro variant to handle the bitname position if it is zero **/ #define BME680_SET_BITS_POS_0(reg_data, bitname, data) \ ((reg_data & ~(bitname##_MSK)) | \ (data & bitname##_MSK)) #define BME680_GET_BITS_POS_0(reg_data, bitname) (reg_data & (bitname##_MSK)) -/** Type definitions */ -/* - * Generic communication function pointer - * @param[in] dev_id: Place holder to store the id of the device structure - * Can be used to store the index of the Chip select or - * I2C address of the device. - * @param[in] reg_addr: Used to select the register the where data needs to - * be read from or written to. - * @param[in/out] reg_data: Data array to read/write - * @param[in] len: Length of the data array +/** Type definitions **/ +/*! + * @brief Generic communication function pointer + * @param[in] dev_id + * Place holder to store the id of the device structure + * Can be used to store the index of the Chip select or + * I2C address of the device. + * @param[in] reg_addr + * Used to select the register the where data needs to + * be read from or written to. + * @param[in/out] reg_data + * Data array to read/write + * @param[in] len + * Length of the data array */ typedef int8_t (*bme680_com_fptr_t)(uint8_t dev_id, uint8_t reg_addr, uint8_t *data, uint16_t len); -/* - * Delay function pointer - * @param[in] period: Time period in milliseconds +/*! + * @brief Delay function pointer + * @param[in] period + * Time period in milliseconds */ typedef void (*bme680_delay_fptr_t)(uint32_t period); @@ -375,7 +362,7 @@ enum bme680_intf { BME680_I2C_INTF }; -/* structure definitions */ +/** structure definitions **/ /*! * @brief Sensor field data structure */ @@ -386,6 +373,8 @@ struct bme680_field_data { uint8_t gas_index; /*! Measurement index to track order */ uint8_t meas_index; + +#ifndef BME680_FLOAT_POINT_COMPENSATION /*! Temperature in degree celsius x100 */ int16_t temperature; /*! Pressure in Pascal */ @@ -394,6 +383,18 @@ struct bme680_field_data { uint32_t humidity; /*! Gas resistance in Ohms */ uint32_t gas_resistance; +#else + /*! Temperature in degree celsius */ + float temperature; + /*! Pressure in Pascal */ + float pressure; + /*! Humidity in % relative humidity x1000 */ + float humidity; + /*! Gas resistance in Ohms */ + float gas_resistance; + +#endif + }; /*! @@ -446,8 +447,14 @@ struct bme680_calib_data { int16_t par_p9; /*! Variable to store calibrated pressure data */ uint8_t par_p10; + +#ifndef BME680_FLOAT_POINT_COMPENSATION /*! Variable to store t_fine size */ int32_t t_fine; +#else + /*! Variable to store t_fine size */ + float t_fine; +#endif /*! Variable to store heater resistance range */ uint8_t res_heat_range; /*! Variable to store heater resistance value */ @@ -458,7 +465,7 @@ struct bme680_calib_data { /*! * @brief BME680 sensor settings structure which comprises of ODR, - * over-sampling and filter settings. + * over-sampling and filter settings. */ struct bme680_tph_sett { /*! Humidity oversampling */ @@ -473,7 +480,7 @@ struct bme680_tph_sett { /*! * @brief BME680 gas sensor which comprises of gas settings - * and status parameters + * and status parameters */ struct bme680_gas_sett { /*! Variable to store nb conversion */ @@ -482,9 +489,9 @@ struct bme680_gas_sett { uint8_t heatr_ctrl; /*! Run gas enable value */ uint8_t run_gas; - /*! Pointer to store heater temperature */ + /*! Heater temperature value */ uint16_t heatr_temp; - /*! Pointer to store duration profile */ + /*! Duration profile value */ uint16_t heatr_dur; }; @@ -500,7 +507,7 @@ struct bme680_dev { enum bme680_intf intf; /*! Memory page used */ uint8_t mem_page; - /*! Ambient temperature in Degree C*/ + /*! Ambient temperature in Degree C */ int8_t amb_temp; /*! Sensor calibration data */ struct bme680_calib_data calib; @@ -514,16 +521,18 @@ struct bme680_dev { uint8_t new_fields; /*! Store the info messages */ uint8_t info_msg; - /*! Burst read structure */ + /*! Bus read function pointer */ bme680_com_fptr_t read; - /*! Burst write structure */ + /*! Bus write function pointer */ bme680_com_fptr_t write; - /*! Delay in ms */ + /*! delay function pointer */ bme680_delay_fptr_t delay_ms; /*! Communication function result */ int8_t com_rslt; }; + + #endif /* BME680_DEFS_H_ */ /** @}*/ /** @}*/ From b179f30ef676a557837b5620ab2664198e03cca5 Mon Sep 17 00:00:00 2001 From: Gregor Hartmann Date: Mon, 9 Dec 2019 13:30:09 +0100 Subject: [PATCH 16/27] Fixes for `ws2812` and `ws2812_effects` (#2953) * clean effects library * Fix several issues in ws2812 and effects * Implement working way of calling shift from callback --- .gitignore | 1 + app/modules/ws2812.c | 121 ++++++------ app/modules/ws2812.h | 25 ++- app/modules/ws2812_effects.c | 279 ++++++++++++++-------------- lua_tests/mispec.lua | 159 ++++++++++++++++ lua_tests/mispec_ws2812.lua | 153 +++++++++++++++ lua_tests/mispec_ws2812_2.lua | 149 +++++++++++++++ lua_tests/mispec_ws2812_effects.lua | 31 ++++ 8 files changed, 721 insertions(+), 197 deletions(-) create mode 100644 lua_tests/mispec.lua create mode 100644 lua_tests/mispec_ws2812.lua create mode 100644 lua_tests/mispec_ws2812_2.lua create mode 100644 lua_tests/mispec_ws2812_effects.lua diff --git a/.gitignore b/.gitignore index 736ae691be..62f8d18182 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ tools/toolchains/ .project .settings/ .vscode +.vs #ignore temp file for build infos buildinfo.h diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c index fa892079c0..8f9e6c4195 100644 --- a/app/modules/ws2812.c +++ b/app/modules/ws2812.c @@ -16,10 +16,6 @@ #define MODE_DUAL 1 - - - - // Init UART1 to be able to stream WS2812 data to GPIO2 pin // If DUAL mode is selected, init UART0 to stream to TXD0 as well // You HAVE to redirect LUA's output somewhere else @@ -168,15 +164,15 @@ static int ws2812_write(lua_State* L) { return 0; } -static ptrdiff_t posrelat (ptrdiff_t pos, size_t len) { +static ptrdiff_t posrelat(ptrdiff_t pos, size_t len) { /* relative string position: negative means back from end */ if (pos < 0) pos += (ptrdiff_t)len + 1; - return (pos >= 0) ? pos : 0; + return MIN(MAX(pos, 1), len); } static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int colorsPerLed) { // Allocate memory - size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds*sizeof(uint8_t); + size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds; ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size); // Associate its metatable @@ -245,17 +241,11 @@ static int ws2812_buffer_fill_lua(lua_State* L) { return 0; } -static int ws2812_buffer_fade(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - const int fade = luaL_checkinteger(L, 2); - unsigned direction = luaL_optinteger( L, 3, FADE_OUT ); - - luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number"); - +void ws2812_buffer_fade(ws2812_buffer * buffer, int fade, unsigned direction) { uint8_t * p = &buffer->values[0]; int val = 0; int i; - for(i = 0; i < buffer->size * buffer->colorsPerLed; i++) + for (i = 0; i < buffer->size * buffer->colorsPerLed; i++) { if (direction == FADE_OUT) { @@ -269,78 +259,101 @@ static int ws2812_buffer_fade(lua_State* L) { *p++ = val; } } +} - return 0; +static int ws2812_buffer_fade_lua(lua_State* L) { + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); + const int fade = luaL_checkinteger(L, 2); + unsigned direction = luaL_optinteger( L, 3, FADE_OUT ); + + luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number"); + + ws2812_buffer_fade(buffer, fade, direction); + + return 0; } +int ws2812_buffer_shift(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ + + ws2812_buffer_shift_prepare* prepare = ws2812_buffer_get_shift_prepare(L, buffer, shiftValue, shift_type, pos_start, pos_end); + ws2812_buffer_shift_prepared(prepare); + // Free memory + luaM_free(L, prepare); + return 0; +} -int ws2812_buffer_shift(ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end) { +ws2812_buffer_shift_prepare* ws2812_buffer_get_shift_prepare(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ ptrdiff_t start = posrelat(pos_start, buffer->size); ptrdiff_t end = posrelat(pos_end, buffer->size); - if (start < 1) start = 1; - if (end > (ptrdiff_t)buffer->size) end = (ptrdiff_t)buffer->size; start--; int size = end - start; size_t offset = start * buffer->colorsPerLed; - //luaL_argcheck(L, shiftValue > 0-size && shiftValue < size, 2, "shifting more elements than buffer size"); + luaL_argcheck(L, shiftValue >= 0-size && shiftValue <= size, 2, "shifting more elements than buffer size"); int shift = shiftValue >= 0 ? shiftValue : -shiftValue; - // check if we want to shift at all - if (shift == 0 || size <= 0) - { - return 0; - } - - uint8_t * tmp_pixels = malloc(buffer->colorsPerLed * sizeof(uint8_t) * shift); - int i,j; size_t shift_len, remaining_len; // calculate length of shift section and remaining section shift_len = shift*buffer->colorsPerLed; remaining_len = (size-shift)*buffer->colorsPerLed; - if (shiftValue > 0) + ws2812_buffer_shift_prepare* prepare = luaM_malloc(L, sizeof(ws2812_buffer_shift_prepare) + shift_len); + prepare->offset = offset; + prepare->tmp_pixels = (uint8_t*)(prepare+1); + prepare->shiftValue = shiftValue; + prepare->shift_len = shift_len; + prepare->remaining_len = remaining_len; + prepare->shift_type = shift_type; + prepare->buffer = buffer; + + return prepare; +} + +void ws2812_buffer_shift_prepared(ws2812_buffer_shift_prepare* prepare) { + + // check if we want to shift at all + if (prepare->shift_len == 0 || (prepare->shift_len + prepare->remaining_len) <= 0) + { + return; + } + + if (prepare->shiftValue > 0) { // Store the values which are moved out of the array (last n pixels) - memcpy(tmp_pixels, &buffer->values[offset + (size-shift)*buffer->colorsPerLed], shift_len); + memcpy(prepare->tmp_pixels, &prepare->buffer->values[prepare->offset + prepare->remaining_len], prepare->shift_len); // Move pixels to end - os_memmove(&buffer->values[offset + shift*buffer->colorsPerLed], &buffer->values[offset], remaining_len); + os_memmove(&prepare->buffer->values[prepare->offset + prepare->shift_len], &prepare->buffer->values[prepare->offset], prepare->remaining_len); // Fill beginning with temp data - if (shift_type == SHIFT_LOGICAL) + if (prepare->shift_type == SHIFT_LOGICAL) { - memset(&buffer->values[offset], 0, shift_len); + memset(&prepare->buffer->values[prepare->offset], 0, prepare->shift_len); } else { - memcpy(&buffer->values[offset], tmp_pixels, shift_len); + memcpy(&prepare->buffer->values[prepare->offset], prepare->tmp_pixels, prepare->shift_len); } } else { // Store the values which are moved out of the array (last n pixels) - memcpy(tmp_pixels, &buffer->values[offset], shift_len); + memcpy(prepare->tmp_pixels, &prepare->buffer->values[prepare->offset], prepare->shift_len); // Move pixels to end - os_memmove(&buffer->values[offset], &buffer->values[offset + shift*buffer->colorsPerLed], remaining_len); + os_memmove(&prepare->buffer->values[prepare->offset], &prepare->buffer->values[prepare->offset + prepare->shift_len], prepare->remaining_len); // Fill beginning with temp data - if (shift_type == SHIFT_LOGICAL) + if (prepare->shift_type == SHIFT_LOGICAL) { - memset(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], 0, shift_len); + memset(&prepare->buffer->values[prepare->offset + prepare->remaining_len], 0, prepare->shift_len); } else { - memcpy(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len); + memcpy(&prepare->buffer->values[prepare->offset + prepare->remaining_len], prepare->tmp_pixels, prepare->shift_len); } } - // Free memory - free(tmp_pixels); - - return 0; } - static int ws2812_buffer_shift_lua(lua_State* L) { ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); @@ -351,11 +364,10 @@ static int ws2812_buffer_shift_lua(lua_State* L) { const int pos_end = luaL_optinteger(L, 5, -1); - ws2812_buffer_shift(buffer, shiftValue, shift_type, pos_start, pos_end); + ws2812_buffer_shift(L, buffer, shiftValue, shift_type, pos_start, pos_end); return 0; } - static int ws2812_buffer_dump(lua_State* L) { ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); @@ -366,8 +378,7 @@ static int ws2812_buffer_dump(lua_State* L) { static int ws2812_buffer_replace(lua_State* L) { ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - size_t l = buffer->size; - ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), l); + ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), buffer->size); uint8_t *src; size_t srcLen; @@ -425,8 +436,8 @@ static int ws2812_buffer_mix(lua_State* L) { val += (int32_t)(source[src].values[i] * source[src].factor); } - val += 128; // rounding istead of floor - val >>= 8; + val += 128; // rounding istead of floor + val /= 256; // do not use implemetation dependant right shift if (val < 0) { val = 0; @@ -501,7 +512,7 @@ static int ws2812_buffer_set(lua_State* L) { // Overflow check if( buffer->colorsPerLed*led + len > buffer->colorsPerLed*buffer->size ) { - return luaL_error(L, "string size will exceed strip length"); + return luaL_error(L, "string size will exceed strip length"); } memcpy(&buffer->values[buffer->colorsPerLed*led], buf, len); @@ -531,8 +542,6 @@ static int ws2812_buffer_sub(lua_State* L) { size_t l = lhs->size; ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); - if (start < 1) start = 1; - if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; if (start <= end) { ws2812_buffer *result = allocate_buffer(L, end - start + 1, lhs->colorsPerLed); memcpy(result->values, lhs->values + lhs->colorsPerLed * (start - 1), lhs->colorsPerLed * (end - start + 1)); @@ -591,10 +600,9 @@ static int ws2812_buffer_tostring(lua_State* L) { return 1; } - LROT_BEGIN(ws2812_buffer) LROT_FUNCENTRY( dump, ws2812_buffer_dump ) - LROT_FUNCENTRY( fade, ws2812_buffer_fade ) + LROT_FUNCENTRY( fade, ws2812_buffer_fade_lua) LROT_FUNCENTRY( fill, ws2812_buffer_fill_lua ) LROT_FUNCENTRY( get, ws2812_buffer_get ) LROT_FUNCENTRY( replace, ws2812_buffer_replace ) @@ -609,8 +617,6 @@ LROT_BEGIN(ws2812_buffer) LROT_FUNCENTRY( __tostring, ws2812_buffer_tostring ) LROT_END( ws2812_buffer, ws2812_buffer, LROT_MASK_INDEX ) - - LROT_BEGIN(ws2812) LROT_FUNCENTRY( init, ws2812_init ) LROT_FUNCENTRY( newBuffer, ws2812_new_buffer ) @@ -623,7 +629,6 @@ LROT_BEGIN(ws2812) LROT_NUMENTRY( SHIFT_CIRCULAR, SHIFT_CIRCULAR ) LROT_END( ws2812, NULL, 0 ) - int luaopen_ws2812(lua_State *L) { // TODO: Make sure that the GPIO system is initialized luaL_rometatable(L, "ws2812.buffer", LROT_TABLEREF(ws2812_buffer)); diff --git a/app/modules/ws2812.h b/app/modules/ws2812.h index 9b31910f62..95fd3f0292 100644 --- a/app/modules/ws2812.h +++ b/app/modules/ws2812.h @@ -17,6 +17,12 @@ #define SHIFT_LOGICAL 0 #define SHIFT_CIRCULAR 1 +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif typedef struct { int size; @@ -24,9 +30,26 @@ typedef struct { uint8_t values[0]; } ws2812_buffer; +typedef struct { + size_t offset; + uint8_t* tmp_pixels; + int shiftValue; + size_t shift_len; + size_t remaining_len; + unsigned shift_type; + ws2812_buffer* buffer; +} ws2812_buffer_shift_prepare; + void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2); -int ws2812_buffer_shift(ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end); +// To shift the lua_State is needed for error message and memory allocation. +// We also need the shift operation inside a timer callback, where we cannot access the lua_State, +// so This is split up in prepare and the actual call, which can be called multiple times with the same prepare object. +// After being done just luaM_free on the prepare object. +void ws2812_buffer_shift_prepared(ws2812_buffer_shift_prepare* prepare); +ws2812_buffer_shift_prepare* ws2812_buffer_get_shift_prepare(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end); + int ws2812_buffer_fill(ws2812_buffer * buffer, int * colors); +void ws2812_buffer_fade(ws2812_buffer * buffer, int fade, unsigned direction); #endif /* APP_MODULES_WS2812_H_ */ diff --git a/app/modules/ws2812_effects.c b/app/modules/ws2812_effects.c index e53676becf..7e30dabca7 100644 --- a/app/modules/ws2812_effects.c +++ b/app/modules/ws2812_effects.c @@ -38,6 +38,10 @@ #define min3(a,b, c) min((a), min((b), (c))) #define max3(a,b, c) max((a), max((b), (c))) +#define IDX_R 1 +#define IDX_G 0 +#define IDX_B 2 +#define IDX_W 3 typedef struct { @@ -54,6 +58,7 @@ typedef struct { uint8_t effect_type; uint8_t color[4]; int effect_int_param1; + ws2812_buffer_shift_prepare* prepare; } ws2812_effects; @@ -91,9 +96,6 @@ static int ws2812_write(ws2812_buffer* buffer) { size_t length1, length2; const char *buffer1, *buffer2; - buffer1 = 0; - length1 = 0; - buffer1 = buffer->values; length1 = buffer->colorsPerLed*buffer->size; @@ -115,11 +117,11 @@ static int ws2812_set_pixel(int pixel, uint32_t color) { uint8_t w = buffer->colorsPerLed == 4 ? ((color & 0xFF000000) >> 24) : 0; int offset = pixel * buffer->colorsPerLed; - buffer->values[offset] = g; - buffer->values[offset+1] = r; - buffer->values[offset+2] = b; + buffer->values[offset+IDX_R] = r; + buffer->values[offset+IDX_G] = g; + buffer->values[offset+IDX_B] = b; if (buffer->colorsPerLed == 4) { - buffer->values[offset+3] = w; + buffer->values[offset+IDX_W] = w; } return 0; @@ -158,12 +160,14 @@ static int ws2812_effects_init(lua_State *L) { luaL_argcheck(L, buffer != NULL, 1, "no valid buffer provided"); // get rid of old state if (state != NULL) { + if (state->prepare) { + luaM_free(L, state->prepare); + } luaL_unref(L, LUA_REGISTRYINDEX, state->buffer_ref); free((void *) state); } // Allocate memory and set all to zero - size_t size = sizeof(ws2812_effects) + buffer->colorsPerLed*sizeof(uint8_t); - state = (ws2812_effects *) calloc(1,size); + state = (ws2812_effects *) calloc(1,sizeof(ws2812_effects)); // initialize state->speed = SPEED_DEFAULT; state->mode_delay = DELAY_DEFAULT; @@ -203,10 +207,10 @@ static int ws2812_effects_get_speed(lua_State* L) { } static int ws2812_effects_set_speed(lua_State* L) { - uint8_t speed = luaL_checkinteger(L, 1); + int speed = luaL_checkinteger(L, 1); luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); - luaL_argcheck(L, speed >= 0 && speed <= 255, 1, "should be a 0-255"); - state->speed = speed; + luaL_argcheck(L, speed >= SPEED_MIN && speed <= SPEED_MAX, 1, "should be 0-255"); + state->speed = (uint8_t)speed; state->mode_delay = 10; return 0; } @@ -230,37 +234,35 @@ static int ws2812_effects_set_delay(lua_State* L) { static int ws2812_effects_set_brightness(lua_State* L) { - uint8_t brightness = luaL_checkint(L, 1); + int brightness = luaL_checkint(L, 1); luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); - luaL_argcheck(L, brightness >= 0 && brightness < 256, 1, "should be a 0-255"); - state->brightness = brightness; + luaL_argcheck(L, brightness >= BRIGHTNESS_MIN && brightness <= BRIGHTNESS_MAX, 1, "should be 0-255"); + state->brightness = (uint8_t) brightness; return 0; } -static int ws2812_effects_fill_buffer(uint32_t color) { +static void ws2812_effects_fill_buffer(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { ws2812_buffer * buffer = state->buffer; - uint8_t g = ((color & 0x00FF0000) >> 16); - uint8_t r = ((color & 0x0000FF00) >> 8); - uint8_t b = (color & 0x000000FF); - uint8_t w = buffer->colorsPerLed == 4 ? ((color & 0xFF000000) >> 24) : 0; + uint8_t bright_g = g * state->brightness / BRIGHTNESS_MAX; + uint8_t bright_r = r * state->brightness / BRIGHTNESS_MAX; + uint8_t bright_b = b * state->brightness / BRIGHTNESS_MAX; + uint8_t bright_w = w * state->brightness / BRIGHTNESS_MAX; // Fill buffer int i; uint8_t * p = &buffer->values[0]; for(i = 0; i < buffer->size; i++) { - *p++ = g * state->brightness / 255; - *p++ = r * state->brightness / 255; - *p++ = b * state->brightness / 255; + *p++ = bright_g; + *p++ = bright_r; + *p++ = bright_b; if (buffer->colorsPerLed == 4) { - *p++ = w * state->brightness / 255; + *p++ = bright_w; } } - - return 0; } @@ -279,9 +281,7 @@ static int ws2812_effects_fill_color() { uint8_t b = state->color[2]; uint8_t w = state->color[3]; - uint32_t color = (w << 24) | (g << 16) | (r << 8) | b; - - ws2812_effects_fill_buffer(color); + ws2812_effects_fill_buffer(r, g, b, w); return 0; } @@ -302,7 +302,7 @@ static int ws2812_effects_mode_blink() { // on ws2812_effects_fill_color(); } - else { + else { // off ws2812_buffer * buffer = state->buffer; memset(&buffer->values[0], 0, buffer->size * buffer->colorsPerLed); @@ -383,9 +383,9 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { // convert to RGB uint32_t grb = hsv2grb(h, s, v); - *p++ = ((grb & 0x00FF0000) >> 16) * state->brightness / 255; - *p++ = ((grb & 0x0000FF00) >> 8) * state->brightness / 255; - *p++ = (grb & 0x000000FF) * state->brightness / 255; + *p++ = ((grb & 0x00FF0000) >> 16) * state->brightness / BRIGHTNESS_MAX; + *p++ = ((grb & 0x0000FF00) >> 8) * state->brightness / BRIGHTNESS_MAX; + *p++ = (grb & 0x000000FF) * state->brightness / BRIGHTNESS_MAX; for (j = 3; j < buffer->colorsPerLed; j++) { *p++ = 0; @@ -442,9 +442,9 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { int steps = numPixels - 1; for(i = 0; i < numPixels; i++) { - *p++ = (g1 + ((g2-g1) * i / steps)) * state->brightness / 255; - *p++ = (r1 + ((r2-r1) * i / steps)) * state->brightness / 255; - *p++ = (b1 + ((b2-b1) * i / steps)) * state->brightness / 255; + *p++ = (g1 + ((g2-g1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; + *p++ = (r1 + ((r2-r1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; + *p++ = (b1 + ((b2-b1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; for (j = 3; j < buffer->colorsPerLed; j++) { *p++ = 0; @@ -465,9 +465,9 @@ static int ws2812_effects_mode_random_color() { ws2812_buffer * buffer = state->buffer; uint32_t color = color_wheel(state->mode_color_index); - uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / 255; - uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / 255; - uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / 255; + uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / BRIGHTNESS_MAX; + uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / BRIGHTNESS_MAX; + uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / BRIGHTNESS_MAX; // Fill buffer int i,j; @@ -500,9 +500,9 @@ static int ws2812_effects_mode_rainbow() { int i,j; uint8_t * p = &buffer->values[0]; for(i = 0; i < buffer->size; i++) { - *p++ = g * state->brightness / 255; - *p++ = r * state->brightness / 255; - *p++ = b * state->brightness / 255; + *p++ = g * state->brightness / BRIGHTNESS_MAX; + *p++ = r * state->brightness / BRIGHTNESS_MAX; + *p++ = b * state->brightness / BRIGHTNESS_MAX; for (j = 3; j < buffer->colorsPerLed; j++) { *p++ = 0; @@ -526,9 +526,9 @@ static int ws2812_effects_mode_rainbow_cycle(int repeat_count) { for(i = 0; i < buffer->size; i++) { uint16_t wheel_index = (i * 360 / buffer->size * repeat_count) % 360; uint32_t color = color_wheel(wheel_index); - uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / 255; - uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / 255; - uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / 255; + uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / BRIGHTNESS_MAX; + uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / BRIGHTNESS_MAX; + uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / BRIGHTNESS_MAX; *p++ = g; *p++ = r; *p++ = b; @@ -565,9 +565,9 @@ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { if(g1<0) g1=0; if(r1<0) r1=0; if(b1<0) b1=0; - *p++ = g1 * state->brightness / 255; - *p++ = r1 * state->brightness / 255; - *p++ = b1 * state->brightness / 255; + *p++ = g1 * state->brightness / BRIGHTNESS_MAX; + *p++ = r1 * state->brightness / BRIGHTNESS_MAX; + *p++ = b1 * state->brightness / BRIGHTNESS_MAX; for (j = 3; j < buffer->colorsPerLed; j++) { *p++ = 0; } @@ -582,13 +582,13 @@ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { static int ws2812_effects_mode_halloween() { ws2812_buffer * buffer = state->buffer; - int g1 = 50 * state->brightness / 255; - int r1 = 255 * state->brightness / 255; - int b1 = 0 * state->brightness / 255; + int g1 = 50 * state->brightness / BRIGHTNESS_MAX; + int r1 = 255 * state->brightness / BRIGHTNESS_MAX; + int b1 = 0 * state->brightness / BRIGHTNESS_MAX; - int g2 = 0 * state->brightness / 255; - int r2 = 255 * state->brightness / 255; - int b2 = 130 * state->brightness / 255; + int g2 = 0 * state->brightness / BRIGHTNESS_MAX; + int r2 = 255 * state->brightness / BRIGHTNESS_MAX; + int b2 = 130 * state->brightness / BRIGHTNESS_MAX; // Fill buffer @@ -612,13 +612,13 @@ static int ws2812_effects_mode_halloween() { static int ws2812_effects_mode_circus_combustus() { ws2812_buffer * buffer = state->buffer; - int g1 = 0 * state->brightness / 255; - int r1 = 255 * state->brightness / 255; - int b1 = 0 * state->brightness / 255; + int g1 = 0 * state->brightness / BRIGHTNESS_MAX; + int r1 = 255 * state->brightness / BRIGHTNESS_MAX; + int b1 = 0 * state->brightness / BRIGHTNESS_MAX; - int g2 = 255 * state->brightness / 255; - int r2 = 255 * state->brightness / 255; - int b2 = 255 * state->brightness / 255; + int g2 = 255 * state->brightness / BRIGHTNESS_MAX; + int r2 = 255 * state->brightness / BRIGHTNESS_MAX; + int b2 = 255 * state->brightness / BRIGHTNESS_MAX; // Fill buffer int i,j; @@ -659,9 +659,7 @@ static int ws2812_effects_mode_larson_scanner() { ws2812_buffer * buffer = state->buffer; int led_index = 0; - for(int i=0; i < buffer->size * buffer->colorsPerLed; i++) { - buffer->values[i] = buffer->values[i] >> 1; - } + ws2812_buffer_fade(buffer, 2, FADE_OUT); uint16_t pos = 0; @@ -694,9 +692,9 @@ static int ws2812_effects_mode_color_wipe() { } else { - uint8_t px_r = state->color[1] * state->brightness / 255; - uint8_t px_g = state->color[0] * state->brightness / 255; - uint8_t px_b = state->color[2] * state->brightness / 255; + uint8_t px_r = state->color[1] * state->brightness / BRIGHTNESS_MAX; + uint8_t px_g = state->color[0] * state->brightness / BRIGHTNESS_MAX; + uint8_t px_b = state->color[2] * state->brightness / BRIGHTNESS_MAX; buffer->values[led_index] = px_g; buffer->values[led_index + 1] = px_r; buffer->values[led_index + 2] = px_b; @@ -769,9 +767,8 @@ static uint32_t ws2812_effects_mode_delay() /** * run loop for the effects. */ -static void ws2812_effects_loop(void *p) +static void ws2812_effects_loop(void* p) { - if (state->effect_type == WS2812_EFFECT_BLINK) { ws2812_effects_mode_blink(); @@ -783,7 +780,7 @@ static void ws2812_effects_loop(void *p) else if (state->effect_type == WS2812_EFFECT_RAINBOW_CYCLE) { // the rainbow cycle effect can be achieved by shifting the buffer - ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + ws2812_buffer_shift_prepared(state->prepare); } else if (state->effect_type == WS2812_EFFECT_FLICKER) { @@ -815,11 +812,11 @@ static void ws2812_effects_loop(void *p) } else if (state->effect_type == WS2812_EFFECT_HALLOWEEN) { - ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + ws2812_buffer_shift_prepared(state->prepare); } else if (state->effect_type == WS2812_EFFECT_CIRCUS_COMBUSTUS) { - ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + ws2812_buffer_shift_prepared(state->prepare); } else if (state->effect_type == WS2812_EFFECT_LARSON_SCANNER) { @@ -827,7 +824,7 @@ static void ws2812_effects_loop(void *p) } else if (state->effect_type == WS2812_EFFECT_CYCLE) { - ws2812_buffer_shift(state->buffer, state->effect_int_param1, SHIFT_CIRCULAR, 1, -1); + ws2812_buffer_shift_prepared(state->prepare); } else if (state->effect_type == WS2812_EFFECT_COLOR_WIPE) { @@ -847,12 +844,21 @@ static void ws2812_effects_loop(void *p) ws2812_write(state->buffer); // set the timer if (state->running == 1 && state->mode_delay >= 10) + if (state->running == 1 && state->mode_delay >= 10) { os_timer_disarm(&(state->os_t)); os_timer_arm(&(state->os_t), state->mode_delay, FALSE); } } +void prepare_shift(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ + // deinit old effect + if (state->prepare) { + luaM_free(L, state->prepare); + } + + state->prepare = ws2812_buffer_get_shift_prepare(L, buffer, shiftValue, shift_type, pos_start, pos_end); +} /** * Set the active effect mode @@ -887,105 +893,102 @@ static int ws2812_effects_set_mode(lua_State* L) { switch (state->effect_type) { case WS2812_EFFECT_STATIC: - // fill with currently set color - ws2812_effects_fill_color(); - state->mode_delay = 250; - break; + // fill with currently set color + ws2812_effects_fill_color(); + state->mode_delay = 250; + break; case WS2812_EFFECT_BLINK: - ws2812_effects_mode_blink(); - break; + ws2812_effects_mode_blink(); + break; case WS2812_EFFECT_GRADIENT: - if(arg_type == LUA_TSTRING) - { - size_t length1; - const char *buffer1 = lua_tolstring(L, 2, &length1); + if(arg_type == LUA_TSTRING) + { + size_t length1; + const char *buffer1 = lua_tolstring(L, 2, &length1); + + if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + { + luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); + } - if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + ws2812_effects_gradient(buffer1, length1); + ws2812_write(state->buffer); + } + else { - luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); + luaL_argerror(L, 2, "string expected"); } - ws2812_effects_gradient(buffer1, length1); - ws2812_write(state->buffer); - } - else - { - luaL_argerror(L, 2, "string expected"); - } - - break; + break; case WS2812_EFFECT_GRADIENT_RGB: - if(arg_type == LUA_TSTRING) - { - size_t length1; - const char *buffer1 = lua_tolstring(L, 2, &length1); + if(arg_type == LUA_TSTRING) + { + size_t length1; + const char *buffer1 = lua_tolstring(L, 2, &length1); - if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + { + luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); + } + + ws2812_effects_gradient_rgb(buffer1, length1); + ws2812_write(state->buffer); + } + else { - luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); + luaL_argerror(L, 2, "string expected"); } - ws2812_effects_gradient_rgb(buffer1, length1); - ws2812_write(state->buffer); - } - else - { - luaL_argerror(L, 2, "string expected"); - } - - break; + break; case WS2812_EFFECT_RANDOM_COLOR: - ws2812_effects_mode_random_color(); - break; + ws2812_effects_mode_random_color(); + break; case WS2812_EFFECT_RAINBOW: - ws2812_effects_mode_rainbow(); - break; + ws2812_effects_mode_rainbow(); + break; case WS2812_EFFECT_RAINBOW_CYCLE: - ws2812_effects_mode_rainbow_cycle(effect_param != EFFECT_PARAM_INVALID ? effect_param : 1); - break; - // flicker + ws2812_effects_mode_rainbow_cycle(effect_param != EFFECT_PARAM_INVALID ? effect_param : 1); + prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + break; case WS2812_EFFECT_FLICKER: - state->effect_int_param1 = effect_param; - break; + state->effect_int_param1 = effect_param; + break; case WS2812_EFFECT_FIRE_FLICKER: case WS2812_EFFECT_FIRE_FLICKER_SOFT: case WS2812_EFFECT_FIRE_FLICKER_INTENSE: - { state->color[0] = 255-40; state->color[1] = 255; state->color[2] = 40; state->color[3] = 0; - } - break; + break; case WS2812_EFFECT_HALLOWEEN: - ws2812_effects_mode_halloween(); - break; + ws2812_effects_mode_halloween(); + prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + break; case WS2812_EFFECT_CIRCUS_COMBUSTUS: - ws2812_effects_mode_circus_combustus(); - break; + ws2812_effects_mode_circus_combustus(); + prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + break; case WS2812_EFFECT_LARSON_SCANNER: - ws2812_effects_mode_larson_scanner(); - break; + ws2812_effects_mode_larson_scanner(); + break; case WS2812_EFFECT_CYCLE: - if (effect_param != EFFECT_PARAM_INVALID) { - state->effect_int_param1 = effect_param; - } - break; + if (effect_param != EFFECT_PARAM_INVALID) { + state->effect_int_param1 = effect_param; + } + prepare_shift(L, state->buffer, state->effect_int_param1, SHIFT_CIRCULAR, 1, -1); + break; case WS2812_EFFECT_COLOR_WIPE: - { - uint32_t black = 0; - ws2812_effects_fill_buffer(black); + // fill buffer with black. r,g,b,w = 0 + ws2812_effects_fill_buffer(0, 0, 0, 0); ws2812_effects_mode_color_wipe(); break; - } case WS2812_EFFECT_RANDOM_DOT: - { // check if more than 1 dot shall be set state->effect_int_param1 = effect_param; - uint32_t black = 0; - ws2812_effects_fill_buffer(black); + // fill buffer with black. r,g,b,w = 0 + ws2812_effects_fill_buffer(0, 0, 0, 0); break; - } } } diff --git a/lua_tests/mispec.lua b/lua_tests/mispec.lua new file mode 100644 index 0000000000..9025f5bdf9 --- /dev/null +++ b/lua_tests/mispec.lua @@ -0,0 +1,159 @@ +local moduleName = ... or 'mispec' +local M = {} +_G[moduleName] = M + +-- Helpers: +function ok(expression, desc) + if expression == nil then expression = false end + desc = desc or 'expression is not ok' + if not expression then + error(desc .. '\n' .. debug.traceback()) + end +end + +function ko(expression, desc) + if expression == nil then expression = false end + desc = desc or 'expression is not ko' + if expression then + error(desc .. '\n' .. debug.traceback()) + end +end + +function eq(a, b) + if type(a) ~= type(b) then + error('type ' .. type(a) .. ' is not equal to ' .. type(b) .. '\n' .. debug.traceback()) + end + if type(a) == 'function' then + return string.dump(a) == string.dump(b) + end + if a == b then return true end + if type(a) ~= 'table' then + error(string.format("%q",tostring(a)) .. ' is not equal to ' .. string.format("%q",tostring(b)) .. '\n' .. debug.traceback()) + end + for k,v in pairs(a) do + if b[k] == nil or not eq(v, b[k]) then return false end + end + for k,v in pairs(b) do + if a[k] == nil or not eq(v, a[k]) then return false end + end + return true +end + +function failwith(message, func, ...) + local status, err = pcall(func, ...) + if status then + local messagePart = "" + if message then + messagePart = " containing \"" .. message .. "\"" + end + error("Error expected" .. messagePart .. '\n' .. debug.traceback()) + end + if (message and not string.find(err, message)) then + error("expected errormessage \"" .. err .. "\" to contain \"" .. message .. "\"" .. '\n' .. debug.traceback() ) + end + return true +end + +function fail(func, ...) + return failwith(nil, func, ...) +end + +local function eventuallyImpl(func, retries, delayMs) + local prevEventually = _G.eventually + _G.eventually = function() error("Cannot nest eventually/andThen.") end + local status, err = pcall(func) + _G.eventually = prevEventually + if status then + M.queuedEventuallyCount = M.queuedEventuallyCount - 1 + M.runNextPending() + else + if retries > 0 then + local t = tmr.create() + t:register(delayMs, tmr.ALARM_SINGLE, M.runNextPending) + t:start() + + table.insert(M.pending, 1, function() eventuallyImpl(func, retries - 1, delayMs) end) + else + M.failed = M.failed + 1 + print("\n ! it failed:", err) + + -- remove all pending eventuallies as spec has failed at this point + for i = 1, M.queuedEventuallyCount - 1 do + table.remove(M.pending, 1) + end + M.queuedEventuallyCount = 0 + M.runNextPending() + end + end +end + +function eventually(func, retries, delayMs) + retries = retries or 10 + delayMs = delayMs or 300 + + M.queuedEventuallyCount = M.queuedEventuallyCount + 1 + + table.insert(M.pending, M.queuedEventuallyCount, function() + eventuallyImpl(func, retries, delayMs) + end) +end + +function andThen(func) + eventually(func, 0, 0) +end + +function describe(name, itshoulds) + M.name = name + M.itshoulds = itshoulds +end + +-- Module: +M.runNextPending = function() + local next = table.remove(M.pending, 1) + if next then + node.task.post(next) + next = nil + else + M.succeeded = M.total - M.failed + local elapsedSeconds = (tmr.now() - M.startTime) / 1000 / 1000 + print(string.format( + '\n\nCompleted in %.2f seconds.\nSuccess rate is %.1f%% (%d failed out of %d).', + elapsedSeconds, 100 * M.succeeded / M.total, M.failed, M.total)) + M.pending = nil + M.queuedEventuallyCount = nil + end +end + +M.run = function() + M.pending = {} + M.queuedEventuallyCount = 0 + M.startTime = tmr.now() + M.total = 0 + M.failed = 0 + local it = {} + it.should = function(_, desc, func) + table.insert(M.pending, function() + uart.write(0, '\n * ' .. desc) + M.total = M.total + 1 + if M.pre then M.pre() end + local status, err = pcall(func) + if not status then + print("\n ! it failed:", err) + M.failed = M.failed + 1 + end + if M.post then M.post() end + M.runNextPending() + end) + end + it.initialize = function(_, pre) M.pre = pre end; + it.cleanup = function(_, post) M.post = post end; + M.itshoulds(it) + + print('' .. M.name .. ', it should:') + M.runNextPending() + + M.itshoulds = nil + M.name = nil +end + +print ("loaded mispec") diff --git a/lua_tests/mispec_ws2812.lua b/lua_tests/mispec_ws2812.lua new file mode 100644 index 0000000000..9538ca0c0b --- /dev/null +++ b/lua_tests/mispec_ws2812.lua @@ -0,0 +1,153 @@ +require 'mispec' + +local buffer, buffer1, buffer2 + +local function initBuffer(buffer, ...) + local i,v + for i,v in ipairs({...}) do + buffer:set(i, v, v*2, v*3, v*4) + end + return buffer +end + +local function equalsBuffer(buffer1, buffer2) + return eq(buffer1:dump(), buffer2:dump()) +end + + +describe('WS2812 buffers', function(it) + + it:should('initialize a buffer', function() + buffer = ws2812.newBuffer(9, 3) + ko(buffer == nil) + ok(eq(buffer:size(), 9), "check size") + ok(eq(buffer:dump(), string.char(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)), "initialize with 0") + + failwith("should be a positive integer", ws2812.newBuffer, 9, 0) + failwith("should be a positive integer", ws2812.newBuffer, 9, -1) + failwith("should be a positive integer", ws2812.newBuffer, 0, 3) + failwith("should be a positive integer", ws2812.newBuffer, -1, 3) + end) + + it:should('have correct size', function() + buffer = ws2812.newBuffer(9, 3) + ok(eq(buffer:size(), 9), "check size") + buffer = ws2812.newBuffer(9, 22) + ok(eq(buffer:size(), 9), "check size") + buffer = ws2812.newBuffer(13, 1) + ok(eq(buffer:size(), 13), "check size") + end) + + it:should('fill a buffer with one color', function() + buffer = ws2812.newBuffer(3, 3) + buffer:fill(1,222,55) + ok(eq(buffer:dump(), string.char(1,222,55,1,222,55,1,222,55)), "RGB") + buffer = ws2812.newBuffer(3, 4) + buffer:fill(1,222,55, 77) + ok(eq(buffer:dump(), string.char(1,222,55,77,1,222,55,77,1,222,55,77)), "RGBW") + end) + + it:should('replace correctly', function() + buffer = ws2812.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255)) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") + + buffer = ws2812.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 2) + ok(eq(buffer:dump(), string.char(0,0,0,3,255,165,33,0,244,12,87,255,0,0,0)), "RGBW") + + buffer = ws2812.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -5) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") + + failwith("Does not fit into destination", function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 4) end) + end) + + it:should('replace correctly issue #2921', function() + local buffer = ws2812.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -7) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") + end) + + it:should('get/set correctly', function() + buffer = ws2812.newBuffer(3, 4) + buffer:fill(1,222,55,13) + ok(eq({buffer:get(2)},{1,222,55,13})) + buffer:set(2, 4,53,99,0) + ok(eq({buffer:get(1)},{1,222,55,13})) + ok(eq({buffer:get(2)},{4,53,99,0})) + ok(eq(buffer:dump(), string.char(1,222,55,13,4,53,99,0,1,222,55,13)), "RGBW") + + failwith("index out of range", function() buffer:get(0) end) + failwith("index out of range", function() buffer:get(4) end) + failwith("index out of range", function() buffer:set(0,1,2,3,4) end) + failwith("index out of range", function() buffer:set(4,1,2,3,4) end) + failwith("number expected, got no value", function() buffer:set(2,1,2,3) end) +-- failwith("extra values given", function() buffer:set(2,1,2,3,4,5) end) + end) + + it:should('fade correctly', function() + buffer = ws2812.newBuffer(1, 3) + buffer:fill(1,222,55) + buffer:fade(2) + ok(buffer:dump() == string.char(0,111,27), "RGB") + buffer:fill(1,222,55) + buffer:fade(3, ws2812.FADE_OUT) + ok(buffer:dump() == string.char(0,222/3,55/3), "RGB") + buffer:fill(1,222,55) + buffer:fade(3, ws2812.FADE_IN) + ok(buffer:dump() == string.char(3,255,165), "RGB") + buffer = ws2812.newBuffer(1, 4) + buffer:fill(1,222,55, 77) + buffer:fade(2, ws2812.FADE_OUT) + ok(eq(buffer:dump(), string.char(0,111,27,38)), "RGBW") + end) + + it:should('mix correctly issue #1736', function() + buffer1 = ws2812.newBuffer(1, 3) + buffer2 = ws2812.newBuffer(1, 3) + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(256/8*7,buffer1,256/8,buffer2) + ok(eq({buffer1:get(1)}, {10,23,54})) + end) + + it:should('mix saturation correctly ', function() + buffer1 = ws2812.newBuffer(1, 3) + buffer2 = ws2812.newBuffer(1, 3) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(256/2,buffer1,-256,buffer2) + ok(eq({buffer1:get(1)}, {0,0,0})) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(25600,buffer1,256/8,buffer2) + ok(eq({buffer1:get(1)}, {255,255,255})) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(-257,buffer1,255,buffer2) + ok(eq({buffer1:get(1)}, {0,5,1})) + end) + + it:should('mix with strings correctly ', function() + buffer1 = ws2812.newBuffer(1, 3) + buffer2 = ws2812.newBuffer(1, 3) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(-257,buffer1:dump(),255,buffer2:dump()) + ok(eq({buffer1:get(1)}, {0,5,1})) + end) + + it:should('power', function() + buffer = ws2812.newBuffer(2, 4) + buffer:fill(10,22,54,234) + ok(eq(buffer:power(), 2*(10+22+54+234))) + end) + +end) + +mispec.run() diff --git a/lua_tests/mispec_ws2812_2.lua b/lua_tests/mispec_ws2812_2.lua new file mode 100644 index 0000000000..a9b4056bbc --- /dev/null +++ b/lua_tests/mispec_ws2812_2.lua @@ -0,0 +1,149 @@ +require 'mispec' + +local buffer, buffer1, buffer2 + +local function initBuffer(buffer, ...) + local i,v + for i,v in ipairs({...}) do + buffer:set(i, v, v*2, v*3, v*4) + end + return buffer +end + +local function equalsBuffer(buffer1, buffer2) + return eq(buffer1:dump(), buffer2:dump()) +end + + +describe('WS2812 buffers', function(it) + + it:should('shift LOGICAL', function() + + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,7,8) + buffer1:shift(2) + ok(equalsBuffer(buffer1, buffer2), "shift right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,0,0) + buffer1:shift(-2) + ok(equalsBuffer(buffer1, buffer2), "shift left") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,0,8,12) + buffer1:shift(1, nil, 2,3) + ok(equalsBuffer(buffer1, buffer2), "shift middle right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,0,12) + buffer1:shift(-1, nil, 2,3) + ok(equalsBuffer(buffer1, buffer2), "shift middle left") + + -- bounds checks, handle gracefully as string:sub does + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,8,9,12,0) + buffer1:shift(-1, ws2812.SHIFT_LOGICAL, 0,5) + ok(equalsBuffer(buffer1, buffer2), "shift left out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,7,8,9) + buffer1:shift(1, ws2812.SHIFT_LOGICAL, 0,5) + ok(equalsBuffer(buffer1, buffer2), "shift right out of bound") + + end) + + it:should('shift LOGICAL issue #2946', function() + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,0,0) + buffer1:shift(4) + ok(equalsBuffer(buffer1, buffer2), "shift all right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,0,0) + buffer1:shift(-4) + ok(equalsBuffer(buffer1, buffer2), "shift all left") + + failwith("shifting more elements than buffer size", function() buffer1:shift(10) end) + failwith("shifting more elements than buffer size", function() buffer1:shift(-6) end) + end) + + it:should('shift CIRCULAR', function() + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,7,8) + buffer1:shift(2, ws2812.SHIFT_CIRCULAR) + ok(equalsBuffer(buffer1, buffer2), "shift right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,7,8) + buffer1:shift(-2, ws2812.SHIFT_CIRCULAR) + ok(equalsBuffer(buffer1, buffer2), "shift left") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,8,12) + buffer1:shift(1, ws2812.SHIFT_CIRCULAR, 2,3) + ok(equalsBuffer(buffer1, buffer2), "shift middle right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,8,12) + buffer1:shift(-1, ws2812.SHIFT_CIRCULAR, 2,3) + ok(equalsBuffer(buffer1, buffer2), "shift middle left") + + -- bounds checks, handle gracefully as string:sub does + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,8,9,12,7) + buffer1:shift(-1, ws2812.SHIFT_CIRCULAR, 0,5) + ok(equalsBuffer(buffer1, buffer2), "shift left out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,12,7,8,9) + buffer1:shift(1, ws2812.SHIFT_CIRCULAR, 0,5) + ok(equalsBuffer(buffer1, buffer2), "shift right out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,12,7,8,9) + buffer1:shift(1, ws2812.SHIFT_CIRCULAR, -12,12) + ok(equalsBuffer(buffer1, buffer2), "shift right way out of bound") + + end) + + it:should('sub', function() + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(4, 4) + initBuffer(buffer1,7,8,9,12) + buffer1 = buffer1:sub(4,3) + ok(eq(buffer1:size(), 0), "sub empty") + + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(2, 4) + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12) + buffer1 = buffer1:sub(3,4) + ok(equalsBuffer(buffer1, buffer2), "sub") + + buffer1 = ws2812.newBuffer(4, 4) + buffer2 = ws2812.newBuffer(4, 4) + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,8,9,12) + buffer1 = buffer1:sub(-12,33) + ok(equalsBuffer(buffer1, buffer2), "out of bounds") + end) + + + + +--[[ +ws2812.buffer:__concat() +--]] + +end) + +mispec.run() diff --git a/lua_tests/mispec_ws2812_effects.lua b/lua_tests/mispec_ws2812_effects.lua new file mode 100644 index 0000000000..3fc8d95b8d --- /dev/null +++ b/lua_tests/mispec_ws2812_effects.lua @@ -0,0 +1,31 @@ +require 'mispec' + +local buffer, buffer1, buffer2 + +describe('WS2812_effects', function(it) + + it:should('set_speed', function() + buffer = ws2812.newBuffer(9, 3) + ws2812_effects.init(buffer) + + ws2812_effects.set_speed(0) + ws2812_effects.set_speed(255) + + failwith("should be", ws2812_effects.set_speed, -1) + failwith("should be", ws2812_effects.set_speed, 256) + end) + + it:should('set_brightness', function() + buffer = ws2812.newBuffer(9, 3) + ws2812_effects.init(buffer) + + ws2812_effects.set_brightness(0) + ws2812_effects.set_brightness(255) + + failwith("should be", ws2812_effects.set_brightness, -1) + failwith("should be", ws2812_effects.set_brightness, 256) + end) + +end) + +mispec.run() From 4fc2b85fde197e92ffa7337fa26c7b30f616d0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Mon, 9 Dec 2019 13:31:55 +0100 Subject: [PATCH 17/27] Streaming support for hx711 (#2915) --- app/modules/hx711.c | 318 ++++++++++++++++++++++++++++++++++++++---- docs/modules/hx711.md | 60 +++++++- 2 files changed, 346 insertions(+), 32 deletions(-) diff --git a/app/modules/hx711.c b/app/modules/hx711.c index 61198e33a1..5277b439fa 100644 --- a/app/modules/hx711.c +++ b/app/modules/hx711.c @@ -3,65 +3,314 @@ #include "module.h" #include "lauxlib.h" +#include "lmem.h" #include "platform.h" #include #include +#include "task/task.h" #include "user_interface.h" static uint8_t data_pin; static uint8_t clk_pin; +// The fields below are after the pin_num conversion +static uint8_t pin_data_pin; +static uint8_t pin_clk_pin; + +#ifdef GPIO_INTERRUPT_ENABLE +static task_handle_t tasknumber; + +// HX711_STATUS can be defined to enable the hx711.status() function to get debug info +#undef HX711_STATUS +#define BUFFERS 2 + +typedef struct { + char *buf[BUFFERS]; + uint32_t dropped[BUFFERS]; + uint32_t timestamp[BUFFERS]; + uint32_t interrupts; + uint32_t hx711_interrupts; + uint16_t buflen; + uint16_t used; + uint32_t nobuffer; + uint8_t active; // slot of the active buffer + uint8_t freed; // slot of the most recently freed buffer + uint8_t mode; + uint8_t dropping; // is non zero when there is no space + int cb_ref; +} CONTROL; + +static CONTROL *control; +#endif /*Lua: hx711.init(clk_pin,data_pin)*/ static int hx711_init(lua_State* L) { - clk_pin = luaL_checkinteger(L,1); - data_pin = luaL_checkinteger(L,2); + clk_pin = luaL_checkint(L,1); + data_pin = luaL_checkint(L,2); MOD_CHECK_ID( gpio, clk_pin ); MOD_CHECK_ID( gpio, data_pin ); platform_gpio_mode(clk_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT); platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); platform_gpio_write(clk_pin,1);//put chip to sleep. + + pin_data_pin = pin_num[data_pin]; + pin_clk_pin = pin_num[clk_pin]; return 0; } +static int32_t ICACHE_RAM_ATTR read_sample(char mode) { + int i; + int32_t data = 0; + + for (i = 0; i < 24 ; i++){ //clock in the 24 bits + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + data = data << 1; + if (GPIO_REG_READ(GPIO_IN_ADDRESS) & (1 << pin_data_pin)) { + data = i == 0 ? -1 : data | 1; //signextend the first bit + } + } + //add 25th-27th clock pulse to prevent protocol error + for (i = 0; i <= mode; i++) { + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, 1 << pin_clk_pin); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, 1 << pin_clk_pin); + } + + return data; +} + +#ifdef GPIO_INTERRUPT_ENABLE +static void ICACHE_RAM_ATTR hx711_data_available() { + if (!control) { + return; + } + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + if (bits & (1 << pin_data_pin)) { + return; // not ready + } + + // Read a sample + int32_t data = read_sample(control->mode); + + if (control->dropping) { + if (control->active == control->freed) { + // still can't advance + control->nobuffer++; + return; + } + // Advance + control->active = (1 + control->active) % BUFFERS; + control->dropping = 0; + } + + // insert into the active buffer + char *dest = control->buf[control->active] + control->used; + *dest++ = data; + *dest++ = data >> 8; + *dest++ = data >> 16; + + control->used += 3; + if (control->used == control->buflen) { + control->used = 0; + control->timestamp[control->active] = system_get_time(); + control->dropped[control->active] = control->nobuffer; + control->nobuffer = 0; + // post task + task_post_medium(tasknumber, control->active); + + uint8_t next_active = (1 + control->active) % BUFFERS; + + if (control->active == control->freed) { + // We can't advance to the buffer + control->dropping = 1; + } else { + // flip to other buffer + control->active = next_active; + } + } +} + +static uint32_t ICACHE_RAM_ATTR hx711_interrupt(uint32_t ret_gpio_status) +{ + // This function really is running at interrupt level with everything + // else masked off. It should take as little time as necessary. + // + // + + // This gets the set of pins which have changed status + uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS); + + int pin_mask = 1 << pin_data_pin; + int i; + + control->interrupts++; + + if (gpio_status & pin_mask) { + uint32_t bits = GPIO_REG_READ(GPIO_IN_ADDRESS); + control->hx711_interrupts++; + if (!(bits & pin_mask)) { + // is now ready to read + hx711_data_available(); + } + GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & pin_mask); + } + + return gpio_status & ~pin_mask; +} + +// Lua: hx711.start( mode, samples, callback ) +static int hx711_start( lua_State* L ) +{ + uint32_t mode = luaL_checkint( L, 1 ); + uint32_t samples = luaL_checkint( L, 2 ); + + if (mode > 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); + } + + if (!samples || samples > 400) { + return luaL_argerror( L, 2, "Samples value out of range (1-400)" ); + } + + if (control) { + return luaL_error( L, "Already running" ); + } + + int buflen = 3 * samples; + + control = (CONTROL *) luaM_malloc(L, sizeof(CONTROL) + BUFFERS * buflen); + if (!control) { + return luaL_error( L, "Failed to allocate memory" ); + } + + int cb_ref; + + if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) { + lua_pushvalue(L, 3); // copy argument (func) to the top of stack + cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); + } else { + luaM_free(L, control); + control = NULL; + return luaL_argerror( L, 3, "Not a callback function" ); + } + + memset(control, 0, sizeof(*control)); + control->buf[0] = (char *) (control + 1); + control->buflen = buflen; + int i; + + for (i = 1; i < BUFFERS; i++) { + control->buf[i] = control->buf[i - 1] + buflen; + } + control->mode = mode; + control->cb_ref = cb_ref; + control->freed = BUFFERS - 1; + + // configure data_pin as interrupt input + platform_gpio_register_intr_hook(1 << pin_data_pin, hx711_interrupt); + platform_gpio_mode(data_pin, PLATFORM_GPIO_INT, PLATFORM_GPIO_FLOAT); + platform_gpio_intr_init(data_pin, GPIO_PIN_INTR_NEGEDGE); + + + // Wake up chip + platform_gpio_write(clk_pin, 0); + + return 0; +} + +// Lua: hx711.stop( ) +static int hx711_stop( lua_State* L ) +{ + if (control) { + platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT); + CONTROL *to_free = control; + control = NULL; + luaL_unref(L, LUA_REGISTRYINDEX, to_free->cb_ref); + luaM_free(L, to_free); + } + + return 0; +} + +static int hx711_status( lua_State* L ) +{ + if (control) { + lua_pushlstring(L, (char *) control, sizeof(*control)); + return 1; + } + + return 0; +} + +static void hx711_task(os_param_t param, uint8_t prio) +{ + (void) prio; + if (!control) { + return; + } + + lua_State *L = lua_getstate(); + + if (control->cb_ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, control->cb_ref); + + lua_pushlstring(L, control->buf[param], control->buflen); + lua_pushinteger(L, control->timestamp[param]); + lua_pushinteger(L, control->dropped[param]); + + control->freed = param; + + lua_call(L, 3, 0); + } +} +#endif + #define HX711_MAX_WAIT 1000000 /*will only read chA@128gain*/ /*Lua: result = hx711.read()*/ -static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { - uint32_t i; - int32_t data = 0; +static int hx711_read(lua_State* L) { + int j; //TODO: double check init has happened first. + // - //wakeup hx711 - platform_gpio_write(clk_pin,0); + uint32_t mode = luaL_optinteger(L, 1, 0); - //wait for data ready. or time out. - //TODO: set pin inturrupt and come back to it. This may take up to 1/10 sec - // or maybe just make an async version too and have both available. - system_soft_wdt_feed(); //clear WDT... this may take a while. - for (i = 0; i 2) { + return luaL_argerror( L, 1, "Mode value out of range" ); } - //Handle timeout error - if (i>=HX711_MAX_WAIT) { - return luaL_error( L, "ADC timeout!", ( unsigned )0 ); +#ifdef GPIO_INTERRUPT_ENABLE + if (control) { + hx711_stop(L); } +#endif + + //wakeup hx711 + platform_gpio_write(clk_pin, 0); + + int32_t data; - for (i = 0; i<24 ; i++){ //clock in the 24 bits - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - data = data<<1; - if (platform_gpio_read(data_pin)==1) { - data = i==0 ? -1 : data|1; //signextend the first bit + // read two samples if mode > 0. We discard the first read and return the + // second value. + for (j = (mode ? 1 : 0); j >= 0; j--) { + uint32_t i; + + //wait for data ready. or time out. + system_soft_wdt_feed(); //clear WDT... this may take a while. + for (i = 0; i= HX711_MAX_WAIT) { + return luaL_error( L, "ADC timeout!"); } + + data = read_sample(mode); } - //add 25th clock pulse to prevent protocol error (probably not needed - // since we'll go to sleep immediately after and reset on wakeup.) - platform_gpio_write(clk_pin,1); - platform_gpio_write(clk_pin,0); - //sleep - platform_gpio_write(clk_pin,1); - lua_pushinteger( L, data ); + + //sleep -- unfortunately, this resets the mode to 0 + platform_gpio_write(clk_pin, 1); + lua_pushinteger(L, data); return 1; } @@ -69,11 +318,20 @@ static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) { LROT_BEGIN(hx711) LROT_FUNCENTRY( init, hx711_init ) LROT_FUNCENTRY( read, hx711_read ) +#ifdef GPIO_INTERRUPT_ENABLE + LROT_FUNCENTRY( start, hx711_start ) +#ifdef HX711_STATUS + LROT_FUNCENTRY( status, hx711_status ) +#endif + LROT_FUNCENTRY( stop, hx711_stop ) +#endif LROT_END( hx711, NULL, 0 ) int luaopen_hx711(lua_State *L) { - // TODO: Make sure that the GPIO system is initialized +#ifdef GPIO_INTERRUPT_ENABLE + tasknumber = task_get_id(hx711_task); +#endif return 0; } diff --git a/docs/modules/hx711.md b/docs/modules/hx711.md index 0eaa3cff19..1095ebae2d 100644 --- a/docs/modules/hx711.md +++ b/docs/modules/hx711.md @@ -2,8 +2,11 @@ | Since | Origin / Contributor | Maintainer | Source | | :----- | :-------------------- | :---------- | :------ | | 2015-10-09 | [Chris Takahashi](https://github.com/christakahashi) | [Chris Takahashi](https://github.com/christakahashi) | [hx711.c](../../app/modules/hx711.c)| +| 2019-04-20 | [Philip Gladstone](https://github.com/pjsg) | [Philip Gladstone](https://github.com/pjsg) -This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. Currently only channel A at 128x gain is supported. +This module provides access to an [HX711 load cell amplifier/ADC](https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide). The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain. The standard Chinese sources have [cheap HX711 boards](https://www.aliexpress.com/wholesale?SearchText=hx711+module) for around $1. + +This can be used for single shot reads, or repetitive reads. Note: To save ROM image space, this module is not compiled into the firmware by default. @@ -35,11 +38,13 @@ Read digital loadcell ADC value. `hx711.read(mode)` #### Parameters -`mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. Currently only channel A @ 128 gain is supported. +- `mode` ADC mode. This parameter specifies which input and the gain to apply to that input. Reading in mode 1 or 2 takes longer than reading in mode 0. |mode | channel | gain | |-----|---------|------| | 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | #### Returns a number (24 bit signed ADC value extended to the machine int size) @@ -49,3 +54,54 @@ a number (24 bit signed ADC value extended to the machine int size) -- Read ch A with 128 gain. raw_data = hx711.read(0) ``` + +## hx711.start() + +Starts to read multiple samples from the ADC. + +#### Syntax +`hx711.start(mode, samples, callback)` + +#### Parameters +- `mode` ADC mode. This parameter is currently ignored and reserved to ensure backward compatibility if support for additional modes is added. +- `samples` The number of samples before the callback is invoked. The length of time depends on the chip's sampling rate. +- `callback` The callback is invoked with three arguments (see below). + +|mode | channel | gain | +|-----|---------|------| +| 0 | A | 128 | +| 1 | B | 32 | +| 2 | A | 64 | + +#### Returns +nothing + +#### Callback +This is invoked every time `samples` samples are read from the HX711. The arguments are: + +- A string which contains `samples` packed 24 bit values. This can be unpacked with the `struct` module (using the "i3" format). +- The time in microseconds of the reception of the last sample in the buffer. +- The number of samples dropped before the start of this buffer (after the end of the previous buffer). + +#### Notes +This api only is built if GPIO_INTERRUPT_ENABLE and GPIO_INTERRUPT_HOOK_ENABLE are defined in the +`user_config.h`. This is the default. + +Also, do not try and mix calls to `start` and calls to `read`. Any calls to `read` will implicitly call `stop` first. + +#### Example +```lua +-- Read ch A with 128 gain. +hx711.start(0, 2, function(s, t, d) local r1, r2, _ = struct.unpack("i3 i3", s) print(r1, r2) end) +``` + +## hx711.stop() + +Stops a previously started set of reads. Any data in buffers is lost. No more callbacks will be invoked. + +#### Syntax +`hx711.stop()` + +#### Returns +nothing + From f85c2780ad035a5e0b2e38cbd30293aaba2649e1 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Mon, 9 Dec 2019 13:33:30 +0100 Subject: [PATCH 18/27] New `net.if.info` call to show LwIP information (#2862) * Remove app/include/netif/wlan_lwip_if.h This file appears to be unused in our tree. * New `net.if.info` call to show LwIP information This is a generalization of `wifi.sta`'s and `wifi.ap`'s `getip` and `getmac` calls. I don't propose to deprecate those, but perhaps we should, in the documentation, point users at this function instead. The direct motivation is to permit continued use of DHCP-provided NTP servers in a future where https://github.com/nodemcu/nodemcu-firmware/pull/2819 has landed, now that https://github.com/nodemcu/nodemcu-firmware/pull/2709 is in the tree. But rather than exposing just that information, a more general interface seems useful. --- app/include/netif/wlan_lwip_if.h | 25 ------------- app/modules/net.c | 61 ++++++++++++++++++++++++++++++++ docs/modules/net.md | 37 +++++++++++++++++++ 3 files changed, 98 insertions(+), 25 deletions(-) delete mode 100644 app/include/netif/wlan_lwip_if.h diff --git a/app/include/netif/wlan_lwip_if.h b/app/include/netif/wlan_lwip_if.h deleted file mode 100644 index 13eff5e454..0000000000 --- a/app/include/netif/wlan_lwip_if.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2011 Espressif System - * -*/ - -#ifndef _WLAN_LWIP_IF_H_ -#define _WLAN_LWIP_IF_H_ - -#define LWIP_IF0_PRIO 28 -#define LWIP_IF1_PRIO 29 - -enum { - SIG_LWIP_RX = 0, -}; - -struct netif * eagle_lwip_if_alloc(struct ieee80211_conn *conn, const uint8 *macaddr, struct ip_info *info); -struct netif * eagle_lwip_getif(uint8 index); - -#ifndef IOT_SIP_MODE -sint8 ieee80211_output_pbuf(struct netif *ifp, struct pbuf* pb); -#else -sint8 ieee80211_output_pbuf(struct ieee80211_conn *conn, esf_buf *eb); -#endif - -#endif /* _WLAN_LWIP_IF_H_ */ diff --git a/app/modules/net.c b/app/modules/net.c index 51efae715b..d8678ec9c8 100644 --- a/app/modules/net.c +++ b/app/modules/net.c @@ -18,6 +18,7 @@ #include "lwip/igmp.h" #include "lwip/tcp.h" #include "lwip/udp.h" +#include "lwip/dhcp.h" #if defined(CLIENT_SSL_ENABLE) && defined(LUA_USE_MODULES_NET) && defined(LUA_USE_MODULES_TLS) #define TLS_MODULE_PRESENT @@ -980,6 +981,62 @@ static int net_getdnsserver( lua_State* L ) { return 1; } +#pragma mark - netif info + +/* + * XXX This is internal to Espressif's SDK, but it's called from several places + * in the NodeMCU tree. It would be nicer if there were a LwIP export for this + * rather than this not-so-secret symbol. + */ +extern struct netif *eagle_lwip_getif(uint8); + +static void +push_ipaddr(lua_State *L, ip_addr_t *addr) { + char temp[20]; + ssize_t ipl = ets_snprintf(temp, sizeof temp, IPSTR, IP2STR(&addr->addr)); + lua_assert (ipl >= 0 && ipl < 20); + lua_pushlstring( L, temp, ipl ); +} + +static void +field_from_ipaddr(lua_State *L, const char * field_name, ip_addr_t* addr) { + if ( ip_addr_isany(addr) ) { + lua_pushnil(L); + } else { + push_ipaddr(L, addr); + } + lua_setfield(L, -2, field_name); +} + +static int net_if_info( lua_State* L ) { + int ifidx = luaL_optint(L, 1, 0); + + struct netif * nif = eagle_lwip_getif(ifidx); + if (nif == NULL) { + return luaL_error( L, "unknown network interface index %d", ifidx); + } + + lua_createtable(L, 0, + 4 + (nif->dhcp == NULL ? 0 : 1)); + + lua_pushlstring(L, nif->hwaddr, nif->hwaddr_len); + lua_setfield(L, -2, "hwaddr"); + + field_from_ipaddr(L, "ip" , &nif->ip_addr); + field_from_ipaddr(L, "netmask", &nif->netmask); + field_from_ipaddr(L, "gateway", &nif->gw); + + if (nif->dhcp != NULL) { + lua_createtable(L, 0, 3); + field_from_ipaddr(L, "server_ip" , &nif->dhcp->server_ip_addr ); + field_from_ipaddr(L, "client_ip" , &nif->dhcp->offered_ip_addr ); + field_from_ipaddr(L, "ntp_server", &nif->dhcp->offered_ntp_addr); + } + lua_setfield(L, -2, "dhcp"); + + return 1; +} + #pragma mark - Tables #ifdef TLS_MODULE_PRESENT @@ -1031,6 +1088,9 @@ LROT_BEGIN(net_dns) LROT_FUNCENTRY( resolve, net_dns_static ) LROT_END( net_dns, net_dns, 0 ) +LROT_BEGIN(net_if) + LROT_FUNCENTRY( info, net_if_info ) +LROT_END(net_if, net_if, 0) LROT_BEGIN(net) LROT_FUNCENTRY( createServer, net_createServer ) @@ -1039,6 +1099,7 @@ LROT_BEGIN(net) LROT_FUNCENTRY( multicastJoin, net_multicastJoin ) LROT_FUNCENTRY( multicastLeave, net_multicastLeave ) LROT_TABENTRY( dns, net_dns ) + LROT_TABENTRY( if, net_if ) #ifdef TLS_MODULE_PRESENT LROT_TABENTRY( cert, tls_cert ) #endif diff --git a/docs/modules/net.md b/docs/modules/net.md index dd1c0ee495..7f6b70e4ff 100644 --- a/docs/modules/net.md +++ b/docs/modules/net.md @@ -618,6 +618,43 @@ Sets the IP of the DNS server used to resolve hostnames. Default: resolver1.open #### See also [`net.dns:getdnsserver()`](#netdnsgetdnsserver) +# net.if Module + +## net.if.info() + +Return information about a network interface, specified by index. + +#### Syntax +`net.if.info(if_index)` + +#### Parameters +- `if_index` the interface index; on ESP8266, `0` is the wifi client (STA) and `1` + is the wifi AP. + +#### Returns +`nil` if the given `if_index` does not correspond to an interface. Otherwise, +a table containing ... + +* `ip`, `netmask`, and `gateway` configured for this interface, as dotted quad strings + or `nil` if none is set. + +* if DHCP was used to configure the interface, then `dhcp` will be a table containing... + + * `server_ip` -- the DHCP server itself, as a dotted quad + + * `client_ip` -- the IP address suggested for the client; likely, this equals `ip` + above, unless the configuration has been overridden. + + * `ntp_server` -- the NTP server suggested by the DHCP server. + +DNS servers are not tracked per-interface in LwIP and, as such, are not +reported here; use [`net.dns:getdnsserver()`](#netdnsgetdnsserver). + +#### Example + +`print(net.if.info(0).dhcp.ntp_server)` will show the NTP server suggested by +the DHCP server. + # net.cert Module This part gone to the [TLS](tls.md) module, link kept for backward compatibility. From d84da3649411a1906bb4839a6719f1eea5acf676 Mon Sep 17 00:00:00 2001 From: Andreas Date: Fri, 13 Dec 2019 11:53:39 +0100 Subject: [PATCH 19/27] fixed missing forward declaration (#2975) --- lua_modules/ds18b20/ds18b20.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lua_modules/ds18b20/ds18b20.lua b/lua_modules/ds18b20/ds18b20.lua index b672c87a35..50d9a34152 100644 --- a/lua_modules/ds18b20/ds18b20.lua +++ b/lua_modules/ds18b20/ds18b20.lua @@ -50,6 +50,8 @@ local function to_string(addr, esc) end end +local conversion + local function readout(self) local next = false local sens = self.sens @@ -114,7 +116,7 @@ local function readout(self) end end -local function conversion(self) +conversion = function (self) local sens = self.sens local powered_only = true for _, s in ipairs(sens) do powered_only = powered_only and s:byte(9) ~= 1 end From d4bc6ce159203f67421a029032f360ce65ec4391 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 26 Dec 2019 18:16:32 +0000 Subject: [PATCH 20/27] mispec: don't use %f The additional reporting, while nice, prevents the use of mispec on integer-only builds --- lua_tests/mispec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua_tests/mispec.lua b/lua_tests/mispec.lua index 9025f5bdf9..13dc1c070d 100644 --- a/lua_tests/mispec.lua +++ b/lua_tests/mispec.lua @@ -117,8 +117,8 @@ M.runNextPending = function() M.succeeded = M.total - M.failed local elapsedSeconds = (tmr.now() - M.startTime) / 1000 / 1000 print(string.format( - '\n\nCompleted in %.2f seconds.\nSuccess rate is %.1f%% (%d failed out of %d).', - elapsedSeconds, 100 * M.succeeded / M.total, M.failed, M.total)) + '\n\nCompleted in %d seconds; %d failed out of %d.', + elapsedSeconds, M.failed, M.total)) M.pending = nil M.queuedEventuallyCount = nil end From 225964f0971ed7f604d8687a4f6db32b7f1c98d8 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 26 Dec 2019 18:17:02 +0000 Subject: [PATCH 21/27] mispec: report status TAPishly --- lua_tests/mispec.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua_tests/mispec.lua b/lua_tests/mispec.lua index 13dc1c070d..c413a30b5a 100644 --- a/lua_tests/mispec.lua +++ b/lua_tests/mispec.lua @@ -137,9 +137,11 @@ M.run = function() M.total = M.total + 1 if M.pre then M.pre() end local status, err = pcall(func) - if not status then - print("\n ! it failed:", err) + if err then + print(("\nTAP: not ok %d # %s: %s"):format(M.total, desc, err)) M.failed = M.failed + 1 + else + print(("\nTAP: ok %d # %s"):format(M.total, desc)) end if M.post then M.post() end M.runNextPending() @@ -149,6 +151,7 @@ M.run = function() it.cleanup = function(_, post) M.post = post end; M.itshoulds(it) + print(("\nTAP: 1..%d"):format(#M.pending)) print('' .. M.name .. ', it should:') M.runNextPending() From 6be91581432e54bb7566193474a038ea0b7834c6 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 26 Dec 2019 18:17:12 +0000 Subject: [PATCH 22/27] mispec: don't use uart.write Prefer print everywhere. --- lua_tests/mispec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua_tests/mispec.lua b/lua_tests/mispec.lua index c413a30b5a..f1cc973c5f 100644 --- a/lua_tests/mispec.lua +++ b/lua_tests/mispec.lua @@ -133,7 +133,7 @@ M.run = function() local it = {} it.should = function(_, desc, func) table.insert(M.pending, function() - uart.write(0, '\n * ' .. desc) + print('\n * ' .. desc) M.total = M.total + 1 if M.pre then M.pre() end local status, err = pcall(func) From 0f6b0fbb87e0cbde254bc4b3309bd95092f1816f Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 24 Dec 2019 01:24:34 +0000 Subject: [PATCH 23/27] expect tests: core library functions --- lua_tests/expectnmcu/core.tcl | 71 +++++++++++++++++++++++++++++++ lua_tests/expectnmcu/pkgIndex.tcl | 11 +++++ 2 files changed, 82 insertions(+) create mode 100644 lua_tests/expectnmcu/core.tcl create mode 100644 lua_tests/expectnmcu/pkgIndex.tcl diff --git a/lua_tests/expectnmcu/core.tcl b/lua_tests/expectnmcu/core.tcl new file mode 100644 index 0000000000..fa2d33e2de --- /dev/null +++ b/lua_tests/expectnmcu/core.tcl @@ -0,0 +1,71 @@ +namespace eval expectnmcu::core { + set panicre "powered by Lua \[0-9.\]+ on SDK \[0-9.\]+" +} + +# Establish a serial connection to the device via socat +proc ::expectnmcu::core::connect { dev baud } { + spawn "socat" "STDIO" "${dev},b${baud},rawer,crnl" + close -onexec 1 -i ${spawn_id} + return ${spawn_id} +} + +# Use DTR/RTS signaling to reboot the device +## I'm not sure why we have to keep resetting the mode, but so it goes. +proc ::expectnmcu::core::reboot { dev baud } { + set victimfd [open "${dev}"] + fconfigure ${victimfd} -mode ${baud},n,8,1 -ttycontrol {DTR 0 RTS 1} + sleep 0.1 + fconfigure ${victimfd} -mode ${baud},n,8,1 -ttycontrol {DTR 0 RTS 0} + close ${victimfd} +} + +proc ::expectnmcu::core::waitboot { victim } { + expect { + -i ${victim} "Formatting file system" { + set timeout 60 + exp_continue + } + -i ${victim} "powered by Lua" { } + timeout { return -code error "Timeout" } + } + # Catch nwf's system bootup, in case we're testing an existing system, + # rather than a blank firmware. + expect { + -i ${victim} -re "Reset delay!\[^\n]*\n> " { + send -i ${victim} "stop()\n" + expect -i ${victim} "> " + } + -i ${victim} "> " { } + timeout { return -code error "Timeout" } + } +} + +proc ::expectnmcu::core::send_exp_prompt { sid cmd } { + send -i ${sid} -- "${cmd}\n" + expect { + -i ${sid} -ex "\n> " { } + -i ${sid} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + timeout { return -code error "Timeout" } + } +} + +proc ::expectnmcu::core::send_exp_res_prompt { sid cmd res } { + send -i ${sid} -- "${cmd}\n" + expect { + -i ${sid} -re "${res}.*\n> " { } + -i ${sid} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + -i ${sid} -re "\n> " { return -code error "Prompt before expected response" } + timeout { return -code error "Timeout" } + } +} + +proc ::expectnmcu::core::send_exp_prompt_c { sid cmd } { + send -i ${sid} -- "${cmd}\n" + expect { + -i ${sid} -ex "\n>> " { } + -i ${sid} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + timeout { return -code error "Timeout" } + } +} + +package provide expectnmcu::core 1.0 diff --git a/lua_tests/expectnmcu/pkgIndex.tcl b/lua_tests/expectnmcu/pkgIndex.tcl new file mode 100644 index 0000000000..5b74575461 --- /dev/null +++ b/lua_tests/expectnmcu/pkgIndex.tcl @@ -0,0 +1,11 @@ +# Tcl package index file, version 1.1 +# This file is generated by the "pkg_mkIndex" command +# and sourced either when an application starts up or +# by a "package unknown" script. It invokes the +# "package ifneeded" command to set up package-related +# information so that packages will be loaded automatically +# in response to "package require" commands. When this +# script is sourced, the variable $dir must contain the +# full path name of this file's directory. + +package ifneeded expectnmcu::core 1.0 [list source [file join $dir core.tcl]] From d6f60a824fb607268e83f55f3a12d32e54d40b86 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 26 Dec 2019 18:15:46 +0000 Subject: [PATCH 24/27] expect tests: file xfer module --- lua_tests/expectnmcu/pkgIndex.tcl | 1 + lua_tests/expectnmcu/xfer.tcl | 80 +++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 lua_tests/expectnmcu/xfer.tcl diff --git a/lua_tests/expectnmcu/pkgIndex.tcl b/lua_tests/expectnmcu/pkgIndex.tcl index 5b74575461..64dbff6987 100644 --- a/lua_tests/expectnmcu/pkgIndex.tcl +++ b/lua_tests/expectnmcu/pkgIndex.tcl @@ -9,3 +9,4 @@ # full path name of this file's directory. package ifneeded expectnmcu::core 1.0 [list source [file join $dir core.tcl]] +package ifneeded expectnmcu::xfer 1.0 [list source [file join $dir xfer.tcl]] diff --git a/lua_tests/expectnmcu/xfer.tcl b/lua_tests/expectnmcu/xfer.tcl new file mode 100644 index 0000000000..0731382be6 --- /dev/null +++ b/lua_tests/expectnmcu/xfer.tcl @@ -0,0 +1,80 @@ +namespace eval expectnmcu::xfer { +} + +package require expectnmcu::core + +proc ::expectnmcu::xfer::open { dev dfh which mode } { + ::expectnmcu::core::send_exp_prompt ${dev} "${dfh} = file.open(\"${which}\",\"${mode}\")" +} + +proc ::expectnmcu::xfer::close { dev dfh } { + ::expectnmcu::core::send_exp_prompt ${dev} "${dfh}:close()" +} + +proc ::expectnmcu::xfer::pwrite { dev dfh where what } { + send -i ${dev} -- [string cat \ + "do local d,e = encoder.fromBase64(\"[binary encode base64 -maxlen 0 ${what}]\");" \ + "${dfh}:seek(\"set\",${where});" \ + "print(${dfh}:write(d));" \ + "end\n" \ + ] + expect { + -i ${dev} -re "true\[\r\n\]+> " { } + -i ${dev} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + -i ${dev} -ex "\n> " { return -code error "Bad result from pwrite" } + timeout { return -code error "Timeout while waiting for pwrite" } + } +} + +proc ::expectnmcu::xfer::pread { dev dfh where howmuch } { + send -i ${dev} -- "${dfh}:seek(\"set\",${where}); print(encoder.toBase64(${dfh}:read(${howmuch})))\n" + expect { + -i ${dev} -re "\\)\\)\\)\[\r\n\]+(\[^\r\n\]+)\[\r\n\]+> " { + return [binary decode base64 ${expect_out(1,string)}] + } + -i ${dev} -ex "\n> " { return -code error "No reply to pread" } + -i ${dev} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + timeout { return -code error "Timeout while pread-ing" } + } +} + +proc ::expectnmcu::xfer::sendfile { dev lfn rfn } { + package require sha256 + + set dfo "xfo" + set xln 48 + + set ltf [::open ${lfn} ] + fconfigure ${ltf} -encoding binary + ::expectnmcu::xfer::open ${dev} ${dfo} ${rfn} "w+" + + set lho [sha2::SHA256Init] + + set fpos 0 + while { 1 } { + set data [read ${ltf} ${xln}] + sha2::SHA256Update ${lho} ${data} + ::expectnmcu::xfer::pwrite ${dev} ${dfo} ${fpos} ${data} + set fpos [expr $fpos + ${xln}] + if { [string length ${data}] != ${xln} } { break } + } + + ::close ${ltf} + ::expectnmcu::xfer::close ${dev} ${dfo} + + set exphash [sha2::Hex [sha2::SHA256Final ${lho}]] + + send -i ${dev} "=encoder.toHex(crypto.fhash(\"sha256\",\"${rfn}\"))\n" + expect { + -i ${dev} -re "\[\r\n\]+(\[a-f0-9\]+)\[\r\n\]+" { + if { ${expect_out(1,string)} != ${exphash} } { + return -code error \ + "Sendfile checksum mismatch: ${expect_out(1,string)} != ${exphash}" + } + } + -i ${dev} -re ${::expectnmcu::core::panicre} { return -code error "Panic!" } + timeout { return -code error "Timeout while verifying checksum" } + } +} + +package provide expectnmcu::xfer 1.0 From c99e2e81d3e1e57b31fa980174528d0cded878fa Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 26 Dec 2019 18:18:07 +0000 Subject: [PATCH 25/27] expect tests: add TAP-based test runner --- lua_tests/tap-driver.expect | 139 ++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 lua_tests/tap-driver.expect diff --git a/lua_tests/tap-driver.expect b/lua_tests/tap-driver.expect new file mode 100644 index 0000000000..5f249b5923 --- /dev/null +++ b/lua_tests/tap-driver.expect @@ -0,0 +1,139 @@ +#!/usr/bin/env expect + +# Push a file to the device, run it, and watch the tests run +# +# A typical invocation looks like: +# TCLLIBPATH=./expectnmcu ./tap-driver.expect -serial /dev/ttyUSB3 ./mispec.lua ./mispec_file.lua +# +# For debugging the driver itself, it may be useful to invoke expect with -d, +# which will give a great deal of diagnostic information about the expect state +# machine's internals: +# +# TCLLIBPATH=./expectnmcu expect -d ./tap-driver.expect ... +# +# The -debug option will turn on some additional reporting from this driver program, as well. + + +package require expectnmcu::core +package require expectnmcu::xfer + +package require cmdline +set cmd_parameters { + { serial.arg "/dev/ttyUSB0" "Set the serial interface name" } + { tpfx.arg "TAP: " "Set the expected TAP test prefix" } + { debug "Enable debugging reporting" } +} +set cmd_usage "- A NodeMCU Lua-based-test runner" +if {[catch {array set cmdopts [cmdline::getoptions ::argv $cmd_parameters $cmd_usage]}]} { + send_user [cmdline::usage $cmd_parameters $cmd_usage] + send_user "\n Additional arguments should be files be transferred\n" + send_user " The last file transferred will be run with `dofile`\n" + exit 0 +} + +proc sus { what } { send_user "\n===> ${what} <===\n" } +proc sui { what } { send_user "\n---> ${what} <---\n" } +proc sud { what } { + upvar 1 cmdopts cmdopts + if { ${cmdopts(debug)} } { send_user "\n~~~> ${what} <~~~\n" } +} + +set victim [::expectnmcu::core::connect ${cmdopts(serial)} 115200] +::expectnmcu::core::reboot ${cmdopts(serial)} 115200 + +# Wait for the system to boot +::expectnmcu::core::waitboot ${victim} +sus "Machine has booted" + +foreach arg ${::argv} { + ::expectnmcu::xfer::sendfile ${victim} ${arg} [file tail ${arg}] +} + +set tfn [file tail [lindex ${::argv} end ] ] +sus "Files transferred; running ${tfn}" + +send -i ${victim} "dofile(\"${tfn}\")\n" +expect -i ${victim} -re "dofile\\(\"${tfn}\"\\)\[\r\n\]+" { } + +set tpfx ${cmdopts(tpfx)} +set toeol ".*(?=\n)" + +# Wait for the test to start and tell us how many +# success lines we should expect +set ntests 5 +set timeout 10 +expect { + -i ${victim} -re "${tpfx}1\\.\\.(\\d+)\r?\n" { + global ntests + set ntests $expect_out(1,string) + } + -i ${victim} -re "${tpfx}Bail out!${toeol}" { + sus "Bail out before start" + exit 2 + } + -i ${dev} -re ${::expectnmcu::core::panicre} { + sus "Panic!" + exit 2 + } + # Consume other outputs and discard as if they were comments + # This must come as the last pattern that looks at input + -re "(?p).${toeol}" { exp_continue } + timeout { + send_user "Failure: time out getting started\n" + exit 2 + } +} + +sus "Expecting ${ntests} test results" + +set timeout 60 +set exitwith 0 +set failures 0 +for {set this 0} {${this} < ${ntests}} {incr this} { + expect { + -i ${victim} -re "${tpfx}#${toeol}" { + sud "Harness got comment: ${expect_out(buffer)}" + exp_continue + } + -i ${victim} -re "${tpfx}ok (\\d+)\\D${toeol}" { + sud "Harness acknowledge OK! ${this} ${expect_out(1,string)}" + set tid ${expect_out(1,string)} + if { ${tid} != [expr ${this} + 1 ]} { + sui "WARNING: Test reporting misaligned at ${this}" + } + } + -i ${victim} -re "${tpfx}not ok (\\d+)\\D${toeol}" { + sud "Failure in simulation after ${this} ${expect_out(1,string)}" + set tid ${expect_out(1,string)} + if { ${tid} != [expr ${this} + 1 ]} { + sui "WARNING: Test reporting misaligned at ${this}" + } + set exitwith [expr max(${exitwith},1)] + set failures [expr ${failures} + 1] + } + -i ${victim} -re "${tpfx}Bail out!${toeol}" { + sud "Bail out in simulation after ${this} tests\n" + exit 2 + } + -i ${dev} -re ${::expectnmcu::core::panicre} { + sus "Panic!" + exit 2 + } + # Consume other outputs and discard as if they were comments + # This must come as the last pattern that looks at input + -re "(?p).${toeol}" { exp_continue } + timeout { + send_user "Failure: time out\n" + exit 2 + } + } +} + +::expectnmcu::core::send_exp_res_prompt ${victim} "print(\"fin\")" "fin" + +if { ${exitwith} == 0 } { + sus "All tests reported in OK" +} else { + sus "${failures} TEST FAILURES; REVIEW LOGS" +} +exit ${exitwith} From 4fc617472e79c7694b13bfeeaf511b4c3b43fb5a Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 24 Dec 2019 01:24:53 +0000 Subject: [PATCH 26/27] expect tests: file module This is probably unnecessary, but it served as a useful stepping stone while building the expect-based library. Probably an on-module test is better for `file` and friends. --- lua_tests/file-test.expect | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lua_tests/file-test.expect diff --git a/lua_tests/file-test.expect b/lua_tests/file-test.expect new file mode 100644 index 0000000000..87b07b9263 --- /dev/null +++ b/lua_tests/file-test.expect @@ -0,0 +1,66 @@ +#!/usr/bin/env expect + +package require expectnmcu::core + +package require cmdline +set cmd_parameters { + { serial.arg "/dev/ttyUSB0" "Set the serial interface name" } +# { debug "Turn on debugging" } +} +set cmd_usage "- A NodeMCU file test program" +if {[catch {array set cmdopts [cmdline::getoptions ::argv $cmd_parameters $cmd_usage]}]} { + send_user [cmdline::usage $cmd_parameters $cmd_usage] + exit 0 +} + +set victim [::expectnmcu::core::connect ${cmdopts(serial)} 115200] +::expectnmcu::core::reboot ${cmdopts(serial)} 115200 +::expectnmcu::core::waitboot ${victim} + +proc sep { cmd } { + upvar 1 victim victim + ::expectnmcu::core::send_exp_prompt ${victim} ${cmd} +} +proc serp { cmd res } { + upvar 1 victim victim + ::expectnmcu::core::send_exp_res_prompt ${victim} ${cmd} ${res} +} + +# Quiesce the network in an attempt to +sep "wifi.sta.autoconnect(0)" + +# Assume we have a filesystem formatted already (which is the default) +# and interrogate it for logging purposes. +sep "=file.fsinfo()" + +sep "file.remove(\"test1\")" +serp "=file.exists(\"test1\")" "false" +sep "file.remove(\"test2\")" +serp "=file.exists(\"test2\")" "false" + +# Test basic model: create, populate, read-back, and delete a file +sep "file.open(\"test1\", \"w+\")" +serp "=file.exists(\"test1\")" "true" +sep "file.write(\"abracadabra\")" +sep "file.seek(\"set\", 0)" +serp "=file.read(11)" "abracadabra" +sep "file.close()" +sep "file.remove(\"test1\")" + +# Test object model by doing that again, twice, interleaved +sep "f1 = file.open(\"test1\", \"w+\")" +serp "=file.exists(\"test1\")" "true" +sep "f1:write(\"abracadabra\")" +sep "f2 = file.open(\"test2\", \"w+\")" +serp "=file.exists(\"test2\")" "true" +sep "f2:write(\"hocus pocus\")" +sep "f1:seek(\"set\", 0)" +sep "f2:seek(\"set\", 0)" +serp "=f1:read(11)" "abracadabra" +serp "=f2:read(11)" "hocus pocus" +sep "f1:close()" +sep "f2:close()" +sep "file.remove(\"test1\")" +sep "file.remove(\"test2\")" + +send_user "\n===> TESTS OK <===\n" From b4843cff7d997885c7b7b26a16f37259220e1a74 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Tue, 24 Dec 2019 01:25:05 +0000 Subject: [PATCH 27/27] expect tests: tls module This one requires much more active participation on the host --- lua_tests/tls-test.expect | 188 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 lua_tests/tls-test.expect diff --git a/lua_tests/tls-test.expect b/lua_tests/tls-test.expect new file mode 100644 index 0000000000..0ccd3b7f7f --- /dev/null +++ b/lua_tests/tls-test.expect @@ -0,0 +1,188 @@ +#!/usr/bin/env expect + +# Walk a NodeMCU device through some basic TLS functionality tests. +# +# Requires `socat` and `openssl` on the host side; tested only on Linux. +# +# Tries to guess the host's IP address using `ip route get`, but this can be +# overridden with the -ip command line option. +# +# A typical invocation looks like: +# TCLLIBPATH=./expectnmcu ./tls-test.expect -serial /dev/ttyUSB3 -wifi "$(cat wificmd)" +# +# where the file `wificmd` contains something like +# wifi.setmode(wifi.STATION); wifi.sta.config({...}); wifi.sta.connect() +# where the ... is filled in with the local network's configuration. All on +# one line, tho', so that the script just gets one prompt back. +# +# For debugging the test itself, it may be useful to invoke expect with -d, +# which will give a great deal of diagnostic information about the expect state +# machine's internals: +# TCLLIBPATH=./expectnmcu expect -d ./tls-test.expect ... + + +package require struct::stack +package require expectnmcu::core + +::struct::stack ulogstack +proc pushulog { new } { + ulogstack push [log_user -info] + log_user ${new} +} +proc populog { } { log_user [ulogstack pop] } + +proc genectls { curve pfx } { + exec "openssl" "ecparam" "-genkey" "-name" ${curve} "-out" "${pfx}.key" + exec "openssl" "req" "-new" "-sha256" "-subj" "/CN=${curve}" "-key" "${pfx}.key" "-out" "${pfx}.csr" + exec "openssl" "req" "-x509" "-sha256" "-days" "365" "-key" "${pfx}.key" "-in" "${pfx}.csr" "-out" "${pfx}.crt" +} + +proc preptls { victim } { + ::expectnmcu::core::send_exp_prompt_c ${victim} "function tlsbasic(id,port,host)" + ::expectnmcu::core::send_exp_prompt_c ${victim} " local c = tls.createConnection()" + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"receive\", function(sck, d) print(\"RECV\",id,d) end)" + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"connection\", function(sck) print(\"CONN\",id); sck:send(\"GET / HTTP/1.0\\r\\n\\r\\n\") end)" + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:on(\"disconnection\", function(sck) print(\"DISC\",id) end)" + ::expectnmcu::core::send_exp_prompt_c ${victim} " c:connect(port,host)" + ::expectnmcu::core::send_exp_prompt_c ${victim} " return c" + ::expectnmcu::core::send_exp_prompt ${victim} "end" +} + +# Basic connectivity test, including disconnection of localsid. +proc basicconntest { id localsid victimsid victimconn } { + set timeout 15 + expect { + -i ${localsid} -re ".+" { + # If socat says anything, it's almost surely an error + exit 1 + } + -i ${victimsid} "CONN\t${id}" { } + } + set timeout 2 + pushulog 0 + expect { + -i ${localsid} "GET / HTTP/1.0\r\n\r\n" { + send -i ${localsid} "abracadabra" + } + } + populog + expect { + -i ${victimsid} "RECV\t${id}\tabracadabra" { + ::expectnmcu::core::send_exp_prompt ${victimsid} "${victimconn}:send(\"test 1 2 3 4\")" + } + } + pushulog 0 + expect { + -i ${localsid} "test 1 2 3 4" { + close -i ${localsid} + } + } + populog + set timeout 15 + expect { + -i ${victimsid} "DISC\t${id}" { } + } +} + +# Generate some TLS certificates for our use, if they don't exist +set fntls256v1 "test-256v1" +if { ! [file exists "${fntls256v1}.key" ] } { genectls "prime256v1" ${fntls256v1} } +set fntls384r1 "test-384r1" +if { ! [file exists "${fntls384r1}.key" ] } { genectls "secp384r1" ${fntls384r1} } + +package require cmdline +set cmd_parameters { + { serial.arg "/dev/ttyUSB0" "Set the serial interface name" } + { wifi.arg "" "Command to run to bring up the network" } + { ip.arg "" "My IP address (will guess if not given)" } +# { debug "Turn on debugging" } +} +set cmd_usage "- A NodeMCU TLS test program" +if {[catch {array set cmdopts [cmdline::getoptions ::argv $cmd_parameters $cmd_usage]}]} { + send_user [cmdline::usage $cmd_parameters $cmd_usage] + exit 0 +} + +# if { ${cmdopts(debug)} } { exp_internal 1 } + +send_user "===> Note: Serial port is ${cmdopts(serial)}; debug is ${cmdopts(debug)} <===\n" + +set victim [::expectnmcu::core::connect ${cmdopts(serial)} 115200] +::expectnmcu::core::reboot ${cmdopts(serial)} 115200 + +# Wait for the system to boot +::expectnmcu::core::waitboot ${victim} +send_user "\n===> Machine has booted <===\n" + +# Program a routine for TLS connections +preptls ${victim} + +# Connect the board to the network + +if {0 < [string length ${cmdopts(wifi)}]} { + ::expectnmcu::core::send_exp_prompt ${victim} ${cmdopts(wifi)} +} + +for {set i 0} {${i} < 10} {incr i} { + send -i ${victim} "=wifi.sta.getip()\n" + expect { + -i ${victim} -re "\n(\[^\n\t]+)\t\[^\t]+\t\[^\t]+\n> " { + set victimip ${expect_out(1,string)} + send_user "\n===> Victim IP address ${victimip} <===\n" + break + } + -i ${victim} -ex "nil\r\n> " { + # must not be connected + sleep 1 + } + } +} +if {10 == $i} { + send_user "\n===> Unable to connect to network; bailing out! <===\n" + exit 1 +} + +if {0 < [string length ${cmdopts(ip)}]} { + set myip ${cmdopts(ip)} +} else { + # Guess our IP address by using the victim's + spawn "ip" "route" "get" ${victimip} + expect { + -re "src (\[^ ]*) " { + set myip ${expect_out(1,string)} + } + } + close +} + +::expectnmcu::core::send_exp_prompt ${victim} "tls.setDebug(2)" +::expectnmcu::core::send_exp_prompt ${victim} "tls.cert.verify(false)" + +send_user "\n===> TEST SSL 256v1, no verify <===\n" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls256v1}.crt,key=${fntls256v1}.key,reuseaddr" +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(0,12345,\"${myip}\")" +basicconntest 0 ${spawn_id} ${victim} "c" + +send_user "\n===> TEST SSL 384r1, no verify <===\n" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(1,12345,\"${myip}\")" +basicconntest 1 ${spawn_id} ${victim} "c" + +send_user "\n===> TEST SSL 384r1, verify <===\n" + +set cert [open "${fntls384r1}.crt"] +::expectnmcu::core::send_exp_prompt_c ${victim} "tls.cert.verify(\[\[" +while { [gets $cert line] >= 0 } { + ::expectnmcu::core::send_exp_prompt_c ${victim} $line +} +::expectnmcu::core::send_exp_prompt ${victim} "]])" +close ${cert} +::expectnmcu::core::send_exp_prompt ${victim} "tls.cert.verify(true)" + +spawn -noecho "socat" "STDIO,cfmakeraw" "OPENSSL-LISTEN:12345,verify=0,certificate=${fntls384r1}.crt,key=${fntls384r1}.key,reuseaddr" +::expectnmcu::core::send_exp_prompt ${victim} "c = tlsbasic(2,12345,\"${myip}\")" +basicconntest 2 ${spawn_id} ${victim} "c" + +send_user "\n===> TESTS OK <===\n"