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/.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/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/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/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/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/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..ff93f0283c 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 @@ -166,30 +166,6 @@ uart_tx_one_char(uint8 uart, uint8 TxChar) return OK; } -/****************************************************************************** - * 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); - } -} - /****************************************************************************** * FunctionName : uart0_tx_buffer * Description : use uart0 to transfer buffer @@ -300,13 +276,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 +302,6 @@ uart_autobaud_timeout(void *timer_arg) } } #include "pm/swtimer.h" - static void uart_init_autobaud(uint32_t uart_no) { @@ -339,22 +316,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 +350,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..6b44591027 --- /dev/null +++ b/app/include/driver/input.h @@ -0,0 +1,10 @@ +#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); + +#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/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/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/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..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 @@ -263,14 +259,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..a3afcf9b46 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" @@ -70,7 +71,7 @@ struct OUTPUT { outBlock buffer; int ndx; uint32_t crc; - int (*fullBlkCB) (void); + void (*fullBlkCB) (void); int flashLen; int flagsLen; int flagsNdx; @@ -79,7 +80,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; @@ -122,7 +122,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; @@ -173,15 +173,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 +194,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 +270,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 +405,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 +440,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 +452,8 @@ 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; + 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 @@ -492,7 +489,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 +538,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_DONE) { 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..520ea02df9 --- /dev/null +++ b/app/lua/lnodemcu.c @@ -0,0 +1,112 @@ +/* +** $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); +/* +** 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 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, (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); + 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..46b5dc3517 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/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..c4a08f6273 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" @@ -67,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 @@ -107,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; @@ -131,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; @@ -172,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; } @@ -187,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) @@ -221,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; @@ -243,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; @@ -250,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_ */ /** @}*/ /** @}*/ 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/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/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/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/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/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/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/app/modules/net.c b/app/modules/net.c index fdbd575d8d..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 @@ -736,6 +737,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 +773,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 +825,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; } @@ -966,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 @@ -1017,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 ) @@ -1025,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/app/modules/node.c b/app/modules/node.c index 167b8b21db..6714a6b874 100644 --- a/app/modules/node.c +++ b/app/modules/node.c @@ -227,69 +227,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(CB, medium_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; } @@ -330,7 +320,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)); } @@ -371,39 +361,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/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/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/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 5dcba1c4b0..2b1f916488 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_isanyfunction(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/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 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/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/app/platform/platform.c b/app/platform/platform.c index a343652cbd..47644dbbc0 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 *) 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 *) 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/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" 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" 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 2b9e97b1c7..25875f2bd8 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/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/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)` 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 + 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 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. diff --git a/docs/modules/node.md b/docs/modules/node.md index 033a9dc668..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 @@ -341,11 +341,7 @@ print(node.info("sw_version").git_release) ## 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)` @@ -360,56 +356,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. 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 95% rename from lua_examples/telnet/telnet.lua rename to lua_examples/telnet/telnet_fifosock.lua index 328d5cbc75..318fe5daf9 100644 --- a/lua_examples/telnet/telnet.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 new file mode 100644 index 0000000000..e33be27763 --- /dev/null +++ b/lua_examples/telnet/telnet_pipe.lua @@ -0,0 +1,69 @@ +--[[ 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. + +]] +--luacheck: no unused args + +local M = {} +local modname = ... +local function telnet_session(socket) + local node = node + local stdout + + 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 + + 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 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 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..64dbff6987 --- /dev/null +++ b/lua_tests/expectnmcu/pkgIndex.tcl @@ -0,0 +1,12 @@ +# 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]] +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 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" diff --git a/lua_tests/mispec.lua b/lua_tests/mispec.lua new file mode 100644 index 0000000000..f1cc973c5f --- /dev/null +++ b/lua_tests/mispec.lua @@ -0,0 +1,162 @@ +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 %d seconds; %d failed out of %d.', + elapsedSeconds, 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() + print('\n * ' .. desc) + M.total = M.total + 1 + if M.pre then M.pre() end + local status, err = pcall(func) + 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() + end) + end + it.initialize = function(_, pre) M.pre = pre end; + 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() + + 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() 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} 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"