diff options
-rw-r--r-- | stmhal/pyexec.c | 60 | ||||
-rw-r--r-- | stmhal/readline.c | 3 | ||||
-rw-r--r-- | stmhal/usbd_cdc_interface.c | 7 | ||||
-rwxr-xr-x[-rw-r--r--] | tools/pyboard.py | 111 |
4 files changed, 138 insertions, 43 deletions
diff --git a/stmhal/pyexec.c b/stmhal/pyexec.c index 08a5866a04..3fd797c0d3 100644 --- a/stmhal/pyexec.c +++ b/stmhal/pyexec.c @@ -54,31 +54,47 @@ pyexec_mode_kind_t pyexec_mode_kind = PYEXEC_MODE_FRIENDLY_REPL; STATIC bool repl_display_debugging_info = 0; +#define EXEC_FLAG_PRINT_EOF (1) +#define EXEC_FLAG_ALLOW_DEBUGGING (2) +#define EXEC_FLAG_IS_REPL (4) + // parses, compiles and executes the code in the lexer // frees the lexer before returning -int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, bool is_repl) { +// EXEC_FLAG_PRINT_EOF prints 2 EOF chars: 1 after normal output, 1 after exception output +// EXEC_FLAG_ALLOW_DEBUGGING allows debugging info to be printed after executing the code +// EXEC_FLAG_IS_REPL is used for REPL inputs (flag passed on to mp_compile) +STATIC int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, int exec_flags) { + int ret = 0; + mp_parse_error_kind_t parse_error_kind; mp_parse_node_t pn = mp_parse(lex, input_kind, &parse_error_kind); qstr source_name = mp_lexer_source_name(lex); + // check for parse error if (pn == MP_PARSE_NODE_NULL) { - // parse error + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + stdout_tx_strn("\x04", 1); + } mp_parse_show_exception(lex, parse_error_kind); mp_lexer_free(lex); - return 0; + goto finish; } mp_lexer_free(lex); - mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, is_repl); + mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, exec_flags & EXEC_FLAG_IS_REPL); + // check for compile error if (mp_obj_is_exception_instance(module_fun)) { + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + stdout_tx_strn("\x04", 1); + } mp_obj_print_exception(module_fun); - return 0; + goto finish; } + // execute code nlr_buf_t nlr; - int ret; uint32_t start = HAL_GetTick(); if (nlr_push(&nlr) == 0) { usb_vcp_set_interrupt_char(VCP_CHAR_CTRL_C); // allow ctrl-C to interrupt us @@ -86,10 +102,17 @@ int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, boo usb_vcp_set_interrupt_char(VCP_CHAR_NONE); // disable interrupt nlr_pop(); ret = 1; + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + stdout_tx_strn("\x04", 1); + } } else { // uncaught exception // FIXME it could be that an interrupt happens just before we disable it here usb_vcp_set_interrupt_char(VCP_CHAR_NONE); // disable interrupt + // print EOF after normal output + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + stdout_tx_strn("\x04", 1); + } // check for SystemExit if (mp_obj_is_subclass_fast(mp_obj_get_type((mp_obj_t)nlr.ret_val), &mp_type_SystemExit)) { // at the moment, the value of SystemExit is unused @@ -101,7 +124,7 @@ int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, boo } // display debugging info if wanted - if (is_repl && repl_display_debugging_info) { + if ((exec_flags & EXEC_FLAG_ALLOW_DEBUGGING) && repl_display_debugging_info) { uint32_t ticks = HAL_GetTick() - start; // TODO implement a function that does this properly printf("took %lu ms\n", ticks); gc_collect(); @@ -123,6 +146,11 @@ int parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, boo } } +finish: + if (exec_flags & EXEC_FLAG_PRINT_EOF) { + stdout_tx_strn("\x04", 1); + } + return ret; } @@ -171,16 +199,13 @@ raw_repl_reset: mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, line.buf, line.len, 0); if (lex == NULL) { - printf("MemoryError\n"); + printf("\x04MemoryError\n\x04"); } else { - int ret = parse_compile_execute(lex, MP_PARSE_FILE_INPUT, false); + int ret = parse_compile_execute(lex, MP_PARSE_FILE_INPUT, EXEC_FLAG_PRINT_EOF); if (ret & PYEXEC_FORCED_EXIT) { return ret; } } - - // indicate end of output with EOF character - stdout_tx_str("\004"); } } @@ -217,6 +242,7 @@ friendly_repl_reset: */ for (;;) { + input_restart: vstr_reset(&line); int ret = readline(&line, ">>> "); @@ -246,7 +272,11 @@ friendly_repl_reset: while (mp_repl_continue_with_input(vstr_str(&line))) { vstr_add_char(&line, '\n'); int ret = readline(&line, "... "); - if (ret == VCP_CHAR_CTRL_D) { + if (ret == VCP_CHAR_CTRL_C) { + // cancel everything + stdout_tx_str("\r\n"); + goto input_restart; + } else if (ret == VCP_CHAR_CTRL_D) { // stop entering compound statement break; } @@ -256,7 +286,7 @@ friendly_repl_reset: if (lex == NULL) { printf("MemoryError\n"); } else { - int ret = parse_compile_execute(lex, MP_PARSE_SINGLE_INPUT, true); + int ret = parse_compile_execute(lex, MP_PARSE_SINGLE_INPUT, EXEC_FLAG_ALLOW_DEBUGGING | EXEC_FLAG_IS_REPL); if (ret & PYEXEC_FORCED_EXIT) { return ret; } @@ -272,7 +302,7 @@ int pyexec_file(const char *filename) { return false; } - return parse_compile_execute(lex, MP_PARSE_FILE_INPUT, false); + return parse_compile_execute(lex, MP_PARSE_FILE_INPUT, 0); } mp_obj_t pyb_set_repl_info(mp_obj_t o_value) { diff --git a/stmhal/readline.c b/stmhal/readline.c index 5ef86c44fa..4b1f48baae 100644 --- a/stmhal/readline.c +++ b/stmhal/readline.c @@ -86,6 +86,9 @@ int readline(vstr_t *line, const char *prompt) { } else if (c == VCP_CHAR_CTRL_A) { // CTRL-A with non-empty line is go-to-start-of-line goto home_key; + } else if (c == VCP_CHAR_CTRL_C) { + // CTRL-C with non-empty line is cancel + return c; } else if (c == VCP_CHAR_CTRL_E) { // CTRL-E is go-to-end-of-line goto end_key; diff --git a/stmhal/usbd_cdc_interface.c b/stmhal/usbd_cdc_interface.c index 28f06067bb..e9b4863f6b 100644 --- a/stmhal/usbd_cdc_interface.c +++ b/stmhal/usbd_cdc_interface.c @@ -364,6 +364,8 @@ static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len) { for (; src < buf_top; src++) { if (*src == user_interrupt_char) { char_found = true; + // raise exception when interrupts are finished + pendsv_nlr_jump(user_interrupt_data); } else { if (char_found) { *dest = *src; @@ -372,11 +374,6 @@ static int8_t CDC_Itf_Receive(uint8_t* Buf, uint32_t *Len) { } } - if (char_found) { - // raise exception when interrupts are finished - pendsv_nlr_jump(user_interrupt_data); - } - // length of remaining characters delta_len = dest - Buf; } diff --git a/tools/pyboard.py b/tools/pyboard.py index 40f2ac934c..d5a01720b7 100644..100755 --- a/tools/pyboard.py +++ b/tools/pyboard.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + """ pyboard interface @@ -19,10 +21,15 @@ To run a script from the local machine on the board and print out the results: This script can also be run directly. To execute a local script, use: + ./pyboard.py test.py + +Or: + python pyboard.py test.py """ +import sys import time import serial @@ -31,21 +38,26 @@ class PyboardError(BaseException): class Pyboard: def __init__(self, serial_device): - self.serial = serial.Serial(serial_device) + self.serial = serial.Serial(serial_device, baudrate=115200, interCharTimeout=1) def close(self): self.serial.close() - def read_until(self, min_num_bytes, ending, timeout=10): + def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): data = self.serial.read(min_num_bytes) + if data_consumer: + data_consumer(data) timeout_count = 0 while True: - if self.serial.inWaiting() > 0: - data = data + self.serial.read(self.serial.inWaiting()) - time.sleep(0.01) - timeout_count = 0 - elif data.endswith(ending): + if data.endswith(ending): break + elif self.serial.inWaiting() > 0: + new_data = self.serial.read(1) + data = data + new_data + if data_consumer: + data_consumer(new_data) + #time.sleep(0.01) + timeout_count = 0 else: timeout_count += 1 if timeout_count >= 10 * timeout: @@ -54,8 +66,12 @@ class Pyboard: return data def enter_raw_repl(self): - self.serial.write(b'\r\x03') # ctrl-C: interrupt any running program + self.serial.write(b'\r\x03\x03') # ctrl-C twice: interrupt any running program self.serial.write(b'\r\x01') # ctrl-A: enter raw REPL + data = self.read_until(1, b'to exit\r\n>') + if not data.endswith(b'raw REPL; CTRL-B to exit\r\n>'): + print(data) + raise PyboardError('could not enter raw repl') self.serial.write(b'\x04') # ctrl-D: soft reset data = self.read_until(1, b'to exit\r\n>') if not data.endswith(b'raw REPL; CTRL-B to exit\r\n>'): @@ -65,31 +81,51 @@ class Pyboard: def exit_raw_repl(self): self.serial.write(b'\r\x02') # ctrl-B: enter friendly REPL - def eval(self, expression): - ret = self.exec('print({})'.format(expression)) - ret = ret.strip() - return ret + def follow(self, data_consumer=False): + # wait for normal output + data = self.read_until(1, b'\x04', data_consumer=data_consumer) + if not data.endswith(b'\x04'): + raise PyboardError('timeout waiting for first EOF reception') + data = data[:-1] - def exec(self, command): + # wait for error output + data_err = self.read_until(2, b'\x04>') + if not data_err.endswith(b'\x04>'): + raise PyboardError('timeout waiting for second EOF reception') + data_err = data_err[:-2] + + # return normal and error output + return data, data_err + + def exec_raw(self, command, data_consumer=False): if isinstance(command, bytes): command_bytes = command else: command_bytes = bytes(command, encoding='ascii') + + # write command for i in range(0, len(command_bytes), 32): self.serial.write(command_bytes[i:min(i+32, len(command_bytes))]) time.sleep(0.01) self.serial.write(b'\x04') + + # check if we could exec command data = self.serial.read(2) if data != b'OK': raise PyboardError('could not exec command') - data = self.read_until(2, b'\x04>') - if not data.endswith(b'\x04>'): - print(data) - raise PyboardError('timeout waiting for EOF reception') - if data.startswith(b'Traceback') or data.startswith(b' File '): - print(data) - raise PyboardError('command failed') - return data[:-2] + + return self.follow(data_consumer) + + def eval(self, expression): + ret = self.exec('print({})'.format(expression)) + ret = ret.strip() + return ret + + def exec(self, command): + ret, ret_err = self.exec_raw(command) + if ret_err: + raise PyboardError('exception', ret, ret_err) + return ret def execfile(self, filename): with open(filename) as f: @@ -175,8 +211,37 @@ def main(): if args.test: run_test(device=args.device) - for file in args.files: - execfile(file, device=args.device) + if len(args.files) == 0: + try: + pyb = Pyboard(args.device) + ret, ret_err = pyb.follow(data_consumer=lambda d:print(str(d, encoding='ascii'), end='')) + pyb.close() + except PyboardError as er: + print(er) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + if ret_err: + print(str(ret_err, encoding='ascii'), end='') + sys.exit(1) + + for filename in args.files: + try: + pyb = Pyboard(args.device) + pyb.enter_raw_repl() + with open(filename) as f: + pyfile = f.read() + ret, ret_err = pyb.exec_raw(pyfile, data_consumer=lambda d:print(str(d, encoding='ascii'), end='')) + pyb.exit_raw_repl() + pyb.close() + except PyboardError as er: + print(er) + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + if ret_err: + print(str(ret_err, encoding='ascii'), end='') + sys.exit(1) if __name__ == "__main__": main() |