From cd19bcbd725df7cb8c8a2a9091a547f93930bb79 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 21 Apr 2017 15:49:17 +1000 Subject: [PATCH 01/14] WIP tools: Add new mprepl tool. --- tools/mprepl.py | 337 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100755 tools/mprepl.py diff --git a/tools/mprepl.py b/tools/mprepl.py new file mode 100755 index 0000000000000..02d14956561af --- /dev/null +++ b/tools/mprepl.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 + +""" +This script gives you a MicroPython REPL prompt and provides a hook on the target +board so that the current directory on the host is mounted on the board, at /remote. + +Usage: + ./mprepl.py [device] + +If not specified, device will default to /dev/ttyACM0. + +To quit from the REPL press Ctrl-X. +""" + +import os +import sys +import time +import struct +import select +import termios +import pyboard + +CMD_STAT = 1 +CMD_LISTDIR_START = 2 +CMD_LISTDIR_NEXT = 3 +CMD_OPEN = 4 +CMD_CLOSE = 5 +CMD_READ = 6 + +fs_hook_code = """\ +import os, select, ustruct as struct, micropython +CMD_STAT = 1 +CMD_LISTDIR_START = 2 +CMD_LISTDIR_NEXT = 3 +CMD_OPEN = 4 +CMD_CLOSE = 5 +CMD_READ = 6 +class RemoteCommand: + def __init__(self): + # TODO sys.stdio doesn't support polling + import sys + self.fout = sys.stdout.buffer + self.fin = sys.stdin.buffer + #import pyb + #self.fout = pyb.USB_VCP() + #self.fin = pyb.USB_VCP() + def rd(self, n): + # implement reading with a timeout in case other side disappears + #res = select.select([self.fin], [], [], 1000) + #if not res[0]: + # raise Exception('timeout waiting for remote response') + return self.fin.read(n) + def begin(self, type): + micropython.kbd_intr(-1) + self.fout.write(bytearray([0x18, type])) + def end(self): + micropython.kbd_intr(3) + def rd_uint32(self): + return struct.unpack(' 0: + buf = pyb.serial.read(n) + console.write(buf) + time.sleep(0.1) + n = pyb.serial.inWaiting() + pyb.serial.write(b'\x01') + pyb.exec_(fs_hook_code) + pyb.serial.write(b'\x02') + time.sleep(0.1) + pyb.serial.read(1) + n = pyb.serial.inWaiting() + while n > 0: + buf = pyb.serial.read(n) + time.sleep(0.1) + n = pyb.serial.inWaiting() + else: + # pass character through to the pyboard + pyb.serial.write(c) + + if pyb.serial.inWaiting() > 0: + c = pyb.serial.read(1) + if c == b'\x18': + # a special command + c = pyb.serial.read(1)[0] + cmd_table[c](cmd) + else: + # pass character through to the console + console.write(c) + +def main(): + if len(sys.argv) == 1: + dev = '/dev/ttyACM0' + else: + dev = sys.argv[1] + + console = Console() + console.enter() + try: + main_loop(console, dev) + finally: + console.exit() + +if __name__ == '__main__': + main() From f5539267cec32edc90c37359a844cd222a9dc489 Mon Sep 17 00:00:00 2001 From: Damien George Date: Sat, 22 Apr 2017 12:13:05 +1000 Subject: [PATCH 02/14] WIP tools: Improve mprepl, can now write and import. --- tools/mprepl.py | 73 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 02d14956561af..60bc36e50c001 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -26,30 +26,43 @@ CMD_OPEN = 4 CMD_CLOSE = 5 CMD_READ = 6 +CMD_WRITE = 7 fs_hook_code = """\ -import os, select, ustruct as struct, micropython +import os, io, select, ustruct as struct, micropython CMD_STAT = 1 CMD_LISTDIR_START = 2 CMD_LISTDIR_NEXT = 3 CMD_OPEN = 4 CMD_CLOSE = 5 CMD_READ = 6 +CMD_WRITE = 7 class RemoteCommand: def __init__(self): - # TODO sys.stdio doesn't support polling - import sys - self.fout = sys.stdout.buffer - self.fin = sys.stdin.buffer - #import pyb - #self.fout = pyb.USB_VCP() - #self.fin = pyb.USB_VCP() + try: + import pyb + self.fout = pyb.USB_VCP() + self.fin = pyb.USB_VCP() + self.can_poll = True + except: + import sys + self.fout = sys.stdout.buffer + self.fin = sys.stdin.buffer + # TODO sys.stdio doesn't support polling + self.can_poll = False + def poll_in(self): + if self.can_poll: + res = select.select([self.fin], [], [], 1000) + if not res[0]: + raise Exception('timeout waiting for remote response') def rd(self, n): # implement reading with a timeout in case other side disappears - #res = select.select([self.fin], [], [], 1000) - #if not res[0]: - # raise Exception('timeout waiting for remote response') + self.poll_in() return self.fin.read(n) + def rdinto(self, buf): + # implement reading with a timeout in case other side disappears + self.poll_in() + return self.fin.readinto(buf) def begin(self, type): micropython.kbd_intr(-1) self.fout.write(bytearray([0x18, type])) @@ -66,6 +79,9 @@ def wr_int32(self, i): def rd_bytes(self): n = struct.unpack(' Date: Wed, 10 May 2017 12:52:20 +1000 Subject: [PATCH 03/14] WIP tools/mprepl: Change listdir to ilistdir. --- tools/mprepl.py | 55 ++++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 60bc36e50c001..6a17dce5ff401 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -21,8 +21,8 @@ import pyboard CMD_STAT = 1 -CMD_LISTDIR_START = 2 -CMD_LISTDIR_NEXT = 3 +CMD_ILISTDIR_START = 2 +CMD_ILISTDIR_NEXT = 3 CMD_OPEN = 4 CMD_CLOSE = 5 CMD_READ = 6 @@ -31,8 +31,8 @@ fs_hook_code = """\ import os, io, select, ustruct as struct, micropython CMD_STAT = 1 -CMD_LISTDIR_START = 2 -CMD_LISTDIR_NEXT = 3 +CMD_ILISTDIR_START = 2 +CMD_ILISTDIR_NEXT = 3 CMD_OPEN = 4 CMD_CLOSE = 5 CMD_READ = 6 @@ -153,19 +153,23 @@ def stat(self, path): if res < 0: raise OSError(-res) return tuple(self.cmd.rd_uint32() for _ in range(10)) - def listdir(self, path): - l = [] - self.cmd.begin(CMD_LISTDIR_START) + def ilistdir(self, path): + self.cmd.begin(CMD_ILISTDIR_START) self.cmd.wr_str(self.path + path) - while True: - self.cmd.begin(CMD_LISTDIR_NEXT) - entry = self.cmd.rd_str() - if entry: - l.append(entry) - else: - break self.cmd.end() - return l + def ilistdir_next(): + while True: + self.cmd.begin(CMD_ILISTDIR_NEXT) + name = self.cmd.rd_str() + if name: + type = self.cmd.rd_uint32() + inode = self.cmd.rd_uint32() + self.cmd.end() + yield (name, type, inode) + else: + self.cmd.end() + break + return ilistdir_next() def open(self, path, mode): self.cmd.begin(CMD_OPEN) self.cmd.wr_str(self.path + path) @@ -239,7 +243,7 @@ def wr_str(self, s): self.fout.write(bytearray([l]) + b) root = './' -data_listdir = [] +data_ilistdir = [] data_files = [] def do_stat(cmd): @@ -254,15 +258,18 @@ def do_stat(cmd): for val in stat: cmd.wr_uint32(val) # TODO will all values always fit in 32 bits? -def do_listdir_start(cmd): - global data_listdir +def do_ilistdir_start(cmd): + global data_ilistdir path = root + cmd.rd_str() - data_listdir = os.listdir(path) + data_ilistdir = os.listdir(path) -def do_listdir_next(cmd): - if data_listdir: - entry = data_listdir.pop(0) +def do_ilistdir_next(cmd): + if data_ilistdir: + entry = data_ilistdir.pop(0) + stat = os.stat(entry) cmd.wr_str(entry) + cmd.wr_uint32(stat.st_mode & 0xc000) + cmd.wr_uint32(stat.st_ino) else: cmd.wr_str('') @@ -306,8 +313,8 @@ def do_write(cmd): cmd_table = { CMD_STAT: do_stat, - CMD_LISTDIR_START: do_listdir_start, - CMD_LISTDIR_NEXT: do_listdir_next, + CMD_ILISTDIR_START: do_ilistdir_start, + CMD_ILISTDIR_NEXT: do_ilistdir_next, CMD_OPEN: do_open, CMD_CLOSE: do_close, CMD_READ: do_read, From 0d20cfac30e1d1c841a859ca79fb4100cb82df90 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 14 May 2019 15:42:59 +1000 Subject: [PATCH 04/14] WIP tools/mprepl: Implement file ioctl, and read without args. --- tools/mprepl.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 6a17dce5ff401..d1282dc5299c9 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -102,6 +102,10 @@ def __init__(self, cmd, fd, is_text): self.cmd = cmd self.fd = fd self.is_text = is_text + def ioctl(self, request, arg): + if request == 4: # CLOSE + self.close() + return 0 def close(self): if self.fd is None: return @@ -109,7 +113,7 @@ def close(self): self.cmd.wr_int32(self.fd) self.cmd.end() self.fd = None - def read(self, n): + def read(self, n=-1): self.cmd.begin(CMD_READ) self.cmd.wr_int32(self.fd) self.cmd.wr_int32(n) From cf2bdf944d9e4d09a7d6d5d391d5eb193edc389b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 15 May 2019 11:23:00 +1000 Subject: [PATCH 05/14] WIP tools/mprepl: Fix readinto, add auto serial detection and shortcuts. --- tools/mprepl.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index d1282dc5299c9..677ad7bdff45d 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -81,7 +81,7 @@ def rd_bytes(self): return self.rd(n) def rd_bytes_into(self, buf): n = struct.unpack(' Date: Thu, 2 Aug 2018 15:13:54 +1000 Subject: [PATCH 06/14] WIP tools/mprepl: Add Windows support. --- tools/mprepl.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 3 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 677ad7bdff45d..e664e0fc6cc99 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -17,8 +17,15 @@ import time import struct import select -import termios import pyboard +from pathlib import Path +try: + import termios + import select +except ImportError: + termios = None + select = None + import msvcrt CMD_STAT = 1 CMD_ILISTDIR_START = 2 @@ -188,7 +195,7 @@ def open(self, path, mode): os.chdir('/remote') """ -class Console: +class ConsolePosix: def __init__(self): self.infd = sys.stdin.fileno() self.infile = sys.stdin.buffer.raw @@ -216,6 +223,86 @@ def readchar(self): def write(self, buf): self.outfile.write(buf) + +class ConsoleWindows: + def enter(self): + pass + + def exit(self): + pass + + def inWaiting(self): + return 1 if msvcrt.kbhit() else 0 + + def readchar(self): + if msvcrt.kbhit(): + ch = msvcrt.getch() + while ch in b'\x00\xe0': # arrow or function key prefix? + if not msvcrt.kbhit(): + return None + ch = msvcrt.getch() # second call returns the actual key code + return ch + + def write(self, buf): + buf = buf.decode() if isinstance(buf, bytes) else buf + sys.stdout.write(buf) + sys.stdout.flush() + # for b in buf: + # if isinstance(b, bytes): + # msvcrt.putch(b) + # else: + # msvcrt.putwch(b) + +if termios: + Console = ConsolePosix + VT_ENABLED = True +else: + Console = ConsoleWindows + + # Windows VT mode ( >= win10 only) + # https://bugs.python.org/msg291732 + import ctypes + from ctypes import wintypes + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + ERROR_INVALID_PARAMETER = 0x0057 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + + def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + LPDWORD = ctypes.POINTER(wintypes.DWORD) + kernel32.GetConsoleMode.errcheck = _check_bool + kernel32.GetConsoleMode.argtypes = (wintypes.HANDLE, LPDWORD) + kernel32.SetConsoleMode.errcheck = _check_bool + kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD) + + def set_conout_mode(new_mode, mask=0xffffffff): + # don't assume StandardOutput is a console. + # open CONOUT$ instead + fdout = os.open('CONOUT$', os.O_RDWR) + try: + hout = msvcrt.get_osfhandle(fdout) + old_mode = wintypes.DWORD() + kernel32.GetConsoleMode(hout, ctypes.byref(old_mode)) + mode = (new_mode & mask) | (old_mode.value & ~mask) + kernel32.SetConsoleMode(hout, mode) + return old_mode.value + finally: + os.close(fdout) + + # def enable_vt_mode(): + mode = mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING + try: + set_conout_mode(mode, mask) + VT_ENABLED=True + except WindowsError as e: + VT_ENABLED=False + + class PyboardCommand: def __init__(self, fin, fout): self.fin = fin @@ -342,7 +429,11 @@ def main_loop(console, dev): console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) while True: - select.select([console.infd, pyb.serial.fd], [], []) # TODO pyb.serial might not have fd + if isinstance(console, ConsolePosix): + select.select([console.infd, pyb.serial.fd], [], []) # TODO pyb.serial might not have fd + else: + while not (console.inWaiting() or pyb.serial.inWaiting()): + time.sleep(0.1) c = console.readchar() if c: if c == b'\x18': # ctrl-X, quit @@ -378,6 +469,14 @@ def main_loop(console, dev): # a special command c = pyb.serial.read(1)[0] cmd_table[c](cmd) + + elif not VT_ENABLED and c == b'\x1b': + # ESC code, ignore these on windows + esctype = pyb.serial.read(1) + if esctype == b'[': # CSI + while not (0x40 < pyb.serial.read(1)[0] < 0x7E): + # Looking for "final byte" of escape sequence + pass else: # pass character through to the console console.write(c) From f998f40a524f925155d912326b9d3f5e302d9a62 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Thu, 2 Aug 2018 15:14:50 +1000 Subject: [PATCH 07/14] WIP tools/mprepl: Windows 64 often has 64bit inodes. --- tools/mprepl.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index e664e0fc6cc99..e68738a6a9f25 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -79,6 +79,10 @@ def rd_uint32(self): return struct.unpack(' Date: Thu, 2 Aug 2018 15:15:25 +1000 Subject: [PATCH 08/14] WIP tools/mprepl: Support passing script file on command line. To run before interactive console. --- tools/mprepl.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index e68738a6a9f25..0edf306b8ade8 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -420,7 +420,7 @@ def do_write(cmd): CMD_WRITE: do_write, } -def main_loop(console, dev): +def main_loop(console, dev, pyfile=None): # TODO add option to not restart pyboard, to continue a previous session try: pyb = pyboard.Pyboard(dev) @@ -436,6 +436,18 @@ def main_loop(console, dev): console.write(bytes('Local directory %s is mounted at /remote\r\n' % root, 'utf8')) console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) + try: + if pyfile: + script = Path(pyfile) + if not script.exists(): + console.write(bytes('\r\nERROR: Provided script not found!\r\n', 'utf8')) + else : + pyb.enter_raw_repl() + pyb.exec_(script.read_bytes()) + pyb.exit_raw_repl() + except: + script = None + while True: if isinstance(console, ConsolePosix): select.select([console.infd, pyb.serial.fd], [], []) # TODO pyb.serial might not have fd @@ -507,11 +519,13 @@ def main(): dev = sys.argv[1] shortcuts = {'a0': '/dev/ttyACM0', 'a1': '/dev/ttyACM1', 'u0': '/dev/ttyUSB0', 'u1': '/dev/ttyUSB1'} dev = shortcuts.get(dev, dev) + + pyfile = sys.argv[2] if len(sys.argv) >= 3 else None console = Console() console.enter() try: - main_loop(console, dev) + main_loop(console, dev, pyfile) finally: console.exit() From 2afc4400b8023d87b284db8548250781eb6021c9 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 6 Aug 2018 13:24:43 +1000 Subject: [PATCH 09/14] WIP tools/mprepl: Add seek to RemoteFile. --- tools/mprepl.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/mprepl.py b/tools/mprepl.py index 0edf306b8ade8..6f62316d3928a 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -34,6 +34,7 @@ CMD_CLOSE = 5 CMD_READ = 6 CMD_WRITE = 7 +CMD_SEEK = 8 fs_hook_code = """\ import os, io, select, ustruct as struct, micropython @@ -44,6 +45,7 @@ CMD_CLOSE = 5 CMD_READ = 6 CMD_WRITE = 7 +CMD_SEEK = 8 class RemoteCommand: def __init__(self): try: @@ -147,6 +149,13 @@ def write(self, buf): n = self.cmd.rd_int32() self.cmd.end() return n + def seek(self, n): + self.cmd.begin(CMD_SEEK) + self.cmd.wr_int32(self.fd) + self.cmd.wr_int32(n) + n = self.cmd.rd_int32() + self.cmd.end() + return n class RemoteFS: def mount(self, readonly, mkfs): @@ -402,6 +411,12 @@ def do_read(cmd): buf = bytes(buf, 'utf8') cmd.wr_bytes(buf) +def do_seek(cmd): + fd = cmd.rd_int32() + n = cmd.rd_int32() + data_files[fd][0].seek(n) + cmd.wr_int32(n) + def do_write(cmd): fd = cmd.rd_int32() buf = cmd.rd_bytes() @@ -418,6 +433,7 @@ def do_write(cmd): CMD_CLOSE: do_close, CMD_READ: do_read, CMD_WRITE: do_write, + CMD_SEEK: do_seek, } def main_loop(console, dev, pyfile=None): From 02926dc0888f1a5769aa69d31cbb07fd056521ec Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 6 Aug 2018 13:27:10 +1000 Subject: [PATCH 10/14] WIP tools/mprepl: Execute all given scripts while in initial raw mode. --- tools/mprepl.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 6f62316d3928a..8cd7e70c7e5b6 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -445,24 +445,21 @@ def main_loop(console, dev, pyfile=None): return pyb.enter_raw_repl() pyb.exec_(fs_hook_code) - pyb.exit_raw_repl() cmd = PyboardCommand(pyb.serial, pyb.serial) console.write(bytes('Connected to MicroPython at %s\r\n' % dev, 'utf8')) console.write(bytes('Local directory %s is mounted at /remote\r\n' % root, 'utf8')) console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) - try: - if pyfile: + if pyfiles: + for pyfile in pyfiles: script = Path(pyfile) if not script.exists(): console.write(bytes('\r\nERROR: Provided script not found!\r\n', 'utf8')) - else : - pyb.enter_raw_repl() + else: pyb.exec_(script.read_bytes()) - pyb.exit_raw_repl() - except: - script = None + + pyb.exit_raw_repl() while True: if isinstance(console, ConsolePosix): From f0c989c7b9eb60c1518c222a1f36f9e815b1253e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 6 Aug 2018 13:28:36 +1000 Subject: [PATCH 11/14] WIP tools/mprepl: Ensure script dir is in sys.path to find pyboard. --- tools/mprepl.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 8cd7e70c7e5b6..90431bbe459c7 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -17,7 +17,6 @@ import time import struct import select -import pyboard from pathlib import Path try: import termios @@ -27,6 +26,13 @@ select = None import msvcrt +tools=Path(__file__).parent +if tools not in sys.path: + sys.path.append(tools) + +import pyboard + + CMD_STAT = 1 CMD_ILISTDIR_START = 2 CMD_ILISTDIR_NEXT = 3 From 0a293bcb09d9157a0d69d7fe80a59667bd515f6b Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 6 Aug 2018 14:07:17 +1000 Subject: [PATCH 12/14] WIP tools/mprepl: Add option to use second serial port for sending data. ... from pc to micropython When the repl serial port is shared between commands and binary data, it can be mixed up if text is written on the console while commands are trying to run in the background. Using the second usb serial port (optionally) available in micropython prvents this from being possible, meaning large blocks of text can be pasted onto the console and it all runs correctly. --- tools/mprepl.py | 67 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 90431bbe459c7..72f4899e950ec 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -17,7 +17,10 @@ import time import struct import select +import argparse from pathlib import Path +import serial +import serial.tools.list_ports try: import termios import select @@ -43,7 +46,7 @@ CMD_SEEK = 8 fs_hook_code = """\ -import os, io, select, ustruct as struct, micropython +import os, io, select, ustruct as struct, micropython, pyb, sys CMD_STAT = 1 CMD_ILISTDIR_START = 2 CMD_ILISTDIR_NEXT = 3 @@ -53,11 +56,14 @@ CMD_WRITE = 7 CMD_SEEK = 8 class RemoteCommand: + use_second_port = False def __init__(self): try: - import pyb self.fout = pyb.USB_VCP() - self.fin = pyb.USB_VCP() + if self.use_second_port: + self.fin = pyb.USB_VCP(1) + else: + self.fin = pyb.USB_VCP() self.can_poll = True except: import sys @@ -210,8 +216,11 @@ def open(self, path, mode): raise OSError(-fd) return RemoteFile(self.cmd, fd, mode.find('b') == -1) -os.mount(RemoteFS(), '/remote') -os.chdir('/remote') +def __mount(use_second_port): + print(use_second_port) + RemoteCommand.use_second_port = use_second_port + os.mount(RemoteFS(), '/remote') + os.chdir('/remote') """ class ConsolePosix: @@ -442,18 +451,37 @@ def do_write(cmd): CMD_SEEK: do_seek, } -def main_loop(console, dev, pyfile=None): +def main_loop(console, dev_in, dev_out, pyfiles): # TODO add option to not restart pyboard, to continue a previous session try: - pyb = pyboard.Pyboard(dev) + pyb = pyboard.Pyboard(dev_in) except pyboard.PyboardError as er: print(er) return + + fout = pyb.serial + if dev_out is not None: + try: + fout = serial.Serial(dev_out) + except serial.SerialException: + port = list(serial.tools.list_ports.grep(dev_out)) + if not port: + raise + for p in port: + try: + fout = serial.Serial(p.device) + break + except serial.SerialException: + pass + + pyb.serial.timeout = 5.0 + pyb.enter_raw_repl() pyb.exec_(fs_hook_code) - cmd = PyboardCommand(pyb.serial, pyb.serial) + pyb.exec_('__mount(%s)' % (dev_out is not None)) + cmd = PyboardCommand(pyb.serial, fout) - console.write(bytes('Connected to MicroPython at %s\r\n' % dev, 'utf8')) + console.write(bytes('Connected to MicroPython at %s\r\n' % dev_in, 'utf8')) console.write(bytes('Local directory %s is mounted at /remote\r\n' % root, 'utf8')) console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) @@ -521,9 +549,19 @@ def main_loop(console, dev, pyfile=None): console.write(c) def main(): + parser = argparse.ArgumentParser( + description="This script gives you a MicroPython REPL prompt and" + " provides a hook on the target board so that the current" + " directory on the host is mounted on the board, at /remote.") + parser.add_argument('port', nargs='?', default='auto', help='micropython repl serial port') + parser.add_argument('--port2', default=None, help='if provided, use second serial port for binary data') + parser.add_argument('--scripts', nargs='*', help='Any provided scripts are run sequentially at startup') + + args = parser.parse_args() + # get serial device - if len(sys.argv) == 1: - dev = None + dev = args.port + if dev == 'auto': for d in ('/dev/ttyACM0', '/dev/ttyACM1', '/dev/ttyUSB0', '/dev/ttyUSB1'): try: os.stat(d) @@ -531,20 +569,17 @@ def main(): continue dev = d break - if dev is None: + if dev == 'auto': print('no device found') sys.exit(1) else: - dev = sys.argv[1] shortcuts = {'a0': '/dev/ttyACM0', 'a1': '/dev/ttyACM1', 'u0': '/dev/ttyUSB0', 'u1': '/dev/ttyUSB1'} dev = shortcuts.get(dev, dev) - pyfile = sys.argv[2] if len(sys.argv) >= 3 else None - console = Console() console.enter() try: - main_loop(console, dev, pyfile) + main_loop(console, dev, args.port2, args.scripts) finally: console.exit() From 7d80767fe0208e19205f3cd087d7cfa0eb91b925 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Mon, 6 Aug 2018 14:08:08 +1000 Subject: [PATCH 13/14] WIP tools/mprelp: When receiving binary data, loop until all data is read. Flush input buffer when a command begins --- tools/mprepl.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 72f4899e950ec..15c09a1bf5931 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -86,6 +86,8 @@ def rdinto(self, buf): return self.fin.readinto(buf) def begin(self, type): micropython.kbd_intr(-1) + while(self.fin.any()): + self.fin.read(1) self.fout.write(bytearray([0x18, type])) def end(self): micropython.kbd_intr(3) @@ -103,10 +105,19 @@ def wr_int32(self, i): self.fout.write(struct.pack(' Date: Mon, 6 Aug 2018 17:20:15 +1000 Subject: [PATCH 14/14] WIP tools/mprepl: Add exit command. On remote repl or in provided script, running exit() with or without exit code, eg exit(-1) will close the script on pc. --- tools/mprepl.py | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tools/mprepl.py b/tools/mprepl.py index 15c09a1bf5931..2a7b527305471 100755 --- a/tools/mprepl.py +++ b/tools/mprepl.py @@ -36,6 +36,7 @@ import pyboard +CMD_EXIT = 0 CMD_STAT = 1 CMD_ILISTDIR_START = 2 CMD_ILISTDIR_NEXT = 3 @@ -47,6 +48,7 @@ fs_hook_code = """\ import os, io, select, ustruct as struct, micropython, pyb, sys +CMD_EXIT = 0 CMD_STAT = 1 CMD_ILISTDIR_START = 2 CMD_ILISTDIR_NEXT = 3 @@ -55,6 +57,13 @@ CMD_READ = 6 CMD_WRITE = 7 CMD_SEEK = 8 + +def exit(code=0): + cmd = RemoteCommand() + cmd.begin(CMD_EXIT) + cmd.wr_int32(code) + cmd.end() + class RemoteCommand: use_second_port = False def __init__(self): @@ -380,6 +389,10 @@ def wr_str(self, s): data_ilistdir = [] data_files = [] +def do_exit(cmd): + exitcode = cmd.rd_int32() + return exitcode + def do_stat(cmd): path = root + cmd.rd_str() try: @@ -452,6 +465,7 @@ def do_write(cmd): cmd.wr_int32(n) cmd_table = { + CMD_EXIT: do_exit, CMD_STAT: do_stat, CMD_ILISTDIR_START: do_ilistdir_start, CMD_ILISTDIR_NEXT: do_ilistdir_next, @@ -494,24 +508,31 @@ def main_loop(console, dev_in, dev_out, pyfiles): console.write(bytes('Connected to MicroPython at %s\r\n' % dev_in, 'utf8')) console.write(bytes('Local directory %s is mounted at /remote\r\n' % root, 'utf8')) - console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) - + repl = True if pyfiles: for pyfile in pyfiles: script = Path(pyfile) if not script.exists(): console.write(bytes('\r\nERROR: Provided script not found!\r\n', 'utf8')) else: - pyb.exec_(script.read_bytes()) + ret = pyb.exec_(script.read_bytes()) + print(ret.strip(b'\x18\x00\r\n').decode()) + if ret.strip(b'\x00\r\n').endswith(b'\x18'): + # Script ends with exit(), don't start repl. + repl = False pyb.exit_raw_repl() - while True: + if repl: + console.write(bytes('Use Ctrl-X to exit this shell\r\n', 'utf8')) + + + while repl: if isinstance(console, ConsolePosix): select.select([console.infd, pyb.serial.fd], [], []) # TODO pyb.serial might not have fd else: while not (console.inWaiting() or pyb.serial.inWaiting()): - time.sleep(0.1) + time.sleep(0.01) c = console.readchar() if c: if c == b'\x18': # ctrl-X, quit @@ -546,7 +567,9 @@ def main_loop(console, dev_in, dev_out, pyfiles): if c == b'\x18': # a special command c = pyb.serial.read(1)[0] - cmd_table[c](cmd) + exitcode = cmd_table[c](cmd) + if exitcode is not None: + return exitcode elif not VT_ENABLED and c == b'\x1b': # ESC code, ignore these on windows @@ -590,9 +613,10 @@ def main(): console = Console() console.enter() try: - main_loop(console, dev, args.port2, args.scripts) + ret = main_loop(console, dev, args.port2, args.scripts) finally: console.exit() + return ret if __name__ == '__main__': - main() + sys.exit(main() or 0)