././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664978585.7684245 mpremote-0.4.0/0000755000175000017500000000000014317307232012661 5ustar00damiendamien././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659966841.0 mpremote-0.4.0/LICENSE0000644000175000017500000000210014274212571013662 0ustar00damiendamienThe MIT License (MIT) Copyright (c) 2021-2022 Damien P. George Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664978585.7684245 mpremote-0.4.0/PKG-INFO0000644000175000017500000000751514317307232013766 0ustar00damiendamienMetadata-Version: 2.1 Name: mpremote Version: 0.4.0 Summary: Tool for interacting remotely with MicroPython Home-page: https://github.com/micropython/micropython Author: Damien George Author-email: damien@micropython.org Project-URL: Bug Tracker, https://github.com/micropython/micropython/issues Classifier: Programming Language :: Python :: 3 Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Requires-Python: >=3.4 Description-Content-Type: text/markdown License-File: LICENSE # mpremote -- MicroPython remote control This CLI tool provides an integrated set of utilities to remotely interact with and automate a MicroPython device over a serial connection. The simplest way to use this tool is: mpremote This will automatically connect to the device and provide an interactive REPL. The full list of supported commands are: mpremote connect -- connect to given device device may be: list, auto, id:x, port:x or any valid device name/path mpremote disconnect -- disconnect current device mpremote mount -- mount local directory on device mpremote eval -- evaluate and print the string mpremote exec -- execute the string mpremote run -- run the given local script mpremote fs -- execute filesystem commands on the device command may be: cat, ls, cp, rm, mkdir, rmdir use ":" as a prefix to specify a file on the device mpremote repl -- enter REPL options: --capture --inject-code --inject-file mpremote mip install -- Install packages (from micropython-lib or third-party sources) options: --target --index --no-mpy mpremote help -- print list of commands and exit Multiple commands can be specified and they will be run sequentially. Connection and disconnection will be done automatically at the start and end of the execution of the tool, if such commands are not explicitly given. Automatic connection will search for the first available serial device. If no action is specified then the REPL will be entered. Shortcuts can be defined using the macro system. Built-in shortcuts are: - a0, a1, a2, a3: connect to `/dev/ttyACM?` - u0, u1, u2, u3: connect to `/dev/ttyUSB?` - c0, c1, c2, c3: connect to `COM?` - cat, ls, cp, rm, mkdir, rmdir, df: filesystem commands - reset: reset the device - bootloader: make the device enter its bootloader Any user configuration, including user-defined shortcuts, can be placed in .config/mpremote/config.py. For example: # Custom macro commands commands = { "c33": "connect id:334D335C3138", "bl": "bootloader", "double x=4": { "command": "eval x*2", "help": "multiply by two" } } Examples: mpremote mpremote a1 mpremote connect /dev/ttyUSB0 repl mpremote ls mpremote a1 ls mpremote exec "import micropython; micropython.mem_info()" mpremote eval 1/2 eval 3/4 mpremote mount . mpremote mount . exec "import local_script" mpremote ls mpremote cat boot.py mpremote cp :main.py . mpremote cp main.py : mpremote cp -r dir/ : mpremote mip install aioble mpremote mip install github:org/repo@branch ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664753594.0 mpremote-0.4.0/README.md0000644000175000017500000000647414316417672014164 0ustar00damiendamien# mpremote -- MicroPython remote control This CLI tool provides an integrated set of utilities to remotely interact with and automate a MicroPython device over a serial connection. The simplest way to use this tool is: mpremote This will automatically connect to the device and provide an interactive REPL. The full list of supported commands are: mpremote connect -- connect to given device device may be: list, auto, id:x, port:x or any valid device name/path mpremote disconnect -- disconnect current device mpremote mount -- mount local directory on device mpremote eval -- evaluate and print the string mpremote exec -- execute the string mpremote run -- run the given local script mpremote fs -- execute filesystem commands on the device command may be: cat, ls, cp, rm, mkdir, rmdir use ":" as a prefix to specify a file on the device mpremote repl -- enter REPL options: --capture --inject-code --inject-file mpremote mip install -- Install packages (from micropython-lib or third-party sources) options: --target --index --no-mpy mpremote help -- print list of commands and exit Multiple commands can be specified and they will be run sequentially. Connection and disconnection will be done automatically at the start and end of the execution of the tool, if such commands are not explicitly given. Automatic connection will search for the first available serial device. If no action is specified then the REPL will be entered. Shortcuts can be defined using the macro system. Built-in shortcuts are: - a0, a1, a2, a3: connect to `/dev/ttyACM?` - u0, u1, u2, u3: connect to `/dev/ttyUSB?` - c0, c1, c2, c3: connect to `COM?` - cat, ls, cp, rm, mkdir, rmdir, df: filesystem commands - reset: reset the device - bootloader: make the device enter its bootloader Any user configuration, including user-defined shortcuts, can be placed in .config/mpremote/config.py. For example: # Custom macro commands commands = { "c33": "connect id:334D335C3138", "bl": "bootloader", "double x=4": { "command": "eval x*2", "help": "multiply by two" } } Examples: mpremote mpremote a1 mpremote connect /dev/ttyUSB0 repl mpremote ls mpremote a1 ls mpremote exec "import micropython; micropython.mem_info()" mpremote eval 1/2 eval 3/4 mpremote mount . mpremote mount . exec "import local_script" mpremote ls mpremote cat boot.py mpremote cp :main.py . mpremote cp main.py : mpremote cp -r dir/ : mpremote mip install aioble mpremote mip install github:org/repo@branch ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1664978585.7684245 mpremote-0.4.0/mpremote/0000755000175000017500000000000014317307232014511 5ustar00damiendamien././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664978550.0 mpremote-0.4.0/mpremote/__init__.py0000644000175000017500000000002614317307166016626 0ustar00damiendamien__version__ = "0.4.0" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659966841.0 mpremote-0.4.0/mpremote/__main__.py0000644000175000017500000000012414274212571016603 0ustar00damiendamien#!/usr/bin/env python3 import sys from mpremote import main sys.exit(main.main()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664978292.0 mpremote-0.4.0/mpremote/commands.py0000644000175000017500000001655214317306564016704 0ustar00damiendamienimport os import sys import tempfile import serial.tools.list_ports from . import pyboardextended as pyboard class CommandError(Exception): pass def do_connect(state, args=None): dev = args.device[0] if args else "auto" do_disconnect(state) try: if dev == "list": # List attached devices. for p in sorted(serial.tools.list_ports.comports()): print( "{} {} {:04x}:{:04x} {} {}".format( p.device, p.serial_number, p.vid if isinstance(p.vid, int) else 0, p.pid if isinstance(p.pid, int) else 0, p.manufacturer, p.product, ) ) # Don't do implicit REPL command. state.did_action() elif dev == "auto": # Auto-detect and auto-connect to the first available device. for p in sorted(serial.tools.list_ports.comports()): try: state.pyb = pyboard.PyboardExtended(p.device, baudrate=115200) return except pyboard.PyboardError as er: if not er.args[0].startswith("failed to access"): raise er raise pyboard.PyboardError("no device found") elif dev.startswith("id:"): # Search for a device with the given serial number. serial_number = dev[len("id:") :] dev = None for p in serial.tools.list_ports.comports(): if p.serial_number == serial_number: state.pyb = pyboard.PyboardExtended(p.device, baudrate=115200) return raise pyboard.PyboardError("no device with serial number {}".format(serial_number)) else: # Connect to the given device. if dev.startswith("port:"): dev = dev[len("port:") :] state.pyb = pyboard.PyboardExtended(dev, baudrate=115200) return except pyboard.PyboardError as er: msg = er.args[0] if msg.startswith("failed to access"): msg += " (it may be in use by another program)" print(msg) sys.exit(1) def do_disconnect(state, _args=None): if not state.pyb: return try: if state.pyb.mounted: if not state.pyb.in_raw_repl: state.pyb.enter_raw_repl(soft_reset=False) state.pyb.umount_local() if state.pyb.in_raw_repl: state.pyb.exit_raw_repl() except OSError: # Ignore any OSError exceptions when shutting down, eg: # - pyboard.filesystem_command will close the connecton if it had an error # - umounting will fail if serial port disappeared pass state.pyb.close() state.pyb = None state._auto_soft_reset = True def show_progress_bar(size, total_size, op="copying"): if not sys.stdout.isatty(): return verbose_size = 2048 bar_length = 20 if total_size < verbose_size: return elif size >= total_size: # Clear progress bar when copy completes print("\r" + " " * (13 + len(op) + bar_length) + "\r", end="") else: bar = size * bar_length // total_size progress = size * 100 // total_size print( "\r ... {} {:3d}% [{}{}]".format(op, progress, "#" * bar, "-" * (bar_length - bar)), end="", ) def do_filesystem(state, args): state.ensure_raw_repl() state.did_action() def _list_recursive(files, path): if os.path.isdir(path): for entry in os.listdir(path): _list_recursive(files, "/".join((path, entry))) else: files.append(os.path.split(path)) command = args.command[0] paths = args.path if command == "cat": # Don't be verbose by default when using cat, so output can be # redirected to something. verbose = args.verbose == True else: verbose = args.verbose != False if command == "cp" and args.recursive: if paths[-1] != ":": raise CommandError("'cp -r' destination must be ':'") paths.pop() src_files = [] for path in paths: if path.startswith(":"): raise CommandError("'cp -r' source files must be local") _list_recursive(src_files, path) known_dirs = {""} state.pyb.exec_("import uos") for dir, file in src_files: dir_parts = dir.split("/") for i in range(len(dir_parts)): d = "/".join(dir_parts[: i + 1]) if d not in known_dirs: state.pyb.exec_("try:\n uos.mkdir('%s')\nexcept OSError as e:\n print(e)" % d) known_dirs.add(d) pyboard.filesystem_command( state.pyb, ["cp", "/".join((dir, file)), ":" + dir + "/"], progress_callback=show_progress_bar, verbose=verbose, ) else: if args.recursive: raise CommandError("'-r' only supported for 'cp'") try: pyboard.filesystem_command( state.pyb, [command] + paths, progress_callback=show_progress_bar, verbose=verbose ) except OSError as er: raise CommandError(er) def do_edit(state, args): state.ensure_raw_repl() state.did_action() if not os.getenv("EDITOR"): raise pyboard.PyboardError("edit: $EDITOR not set") for src in args.files: src = src.lstrip(":") dest_fd, dest = tempfile.mkstemp(suffix=os.path.basename(src)) try: print("edit :%s" % (src,)) os.close(dest_fd) state.pyb.fs_touch(src) state.pyb.fs_get(src, dest, progress_callback=show_progress_bar) if os.system("$EDITOR '%s'" % (dest,)) == 0: state.pyb.fs_put(dest, src, progress_callback=show_progress_bar) finally: os.unlink(dest) def _do_execbuffer(state, buf, follow): state.ensure_raw_repl() state.did_action() try: state.pyb.exec_raw_no_follow(buf) if follow: ret, ret_err = state.pyb.follow(timeout=None, data_consumer=pyboard.stdout_write_bytes) if ret_err: pyboard.stdout_write_bytes(ret_err) sys.exit(1) except pyboard.PyboardError as er: print(er) sys.exit(1) except KeyboardInterrupt: sys.exit(1) def do_exec(state, args): _do_execbuffer(state, args.expr[0], args.follow) def do_eval(state, args): buf = "print(" + args.expr[0] + ")" _do_execbuffer(state, buf, True) def do_run(state, args): filename = args.path[0] try: with open(filename, "rb") as f: buf = f.read() except OSError: raise CommandError(f"could not read file '{filename}'") _do_execbuffer(state, buf, args.follow) def do_mount(state, args): state.ensure_raw_repl() path = args.path[0] state.pyb.mount_local(path, unsafe_links=args.unsafe_links) print(f"Local directory {path} is mounted at /remote") def do_umount(state, path): state.ensure_raw_repl() state.pyb.umount_local() def do_resume(state, _args=None): state._auto_soft_reset = False def do_soft_reset(state, _args=None): state.ensure_raw_repl(soft_reset=True) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1659966841.0 mpremote-0.4.0/mpremote/console.py0000644000175000017500000001224514274212571016534 0ustar00damiendamienimport sys, time try: import select, termios except ImportError: termios = None select = None import msvcrt, signal class ConsolePosix: def __init__(self): self.infd = sys.stdin.fileno() self.infile = sys.stdin.buffer self.outfile = sys.stdout.buffer if hasattr(self.infile, "raw"): self.infile = self.infile.raw if hasattr(self.outfile, "raw"): self.outfile = self.outfile.raw self.orig_attr = termios.tcgetattr(self.infd) def enter(self): # attr is: [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] attr = termios.tcgetattr(self.infd) attr[0] &= ~( termios.BRKINT | termios.ICRNL | termios.INPCK | termios.ISTRIP | termios.IXON ) attr[1] = 0 attr[2] = attr[2] & ~(termios.CSIZE | termios.PARENB) | termios.CS8 attr[3] = 0 attr[6][termios.VMIN] = 1 attr[6][termios.VTIME] = 0 termios.tcsetattr(self.infd, termios.TCSANOW, attr) def exit(self): termios.tcsetattr(self.infd, termios.TCSANOW, self.orig_attr) def waitchar(self, pyb_serial): # TODO pyb_serial might not have fd select.select([self.infd, pyb_serial.fd], [], []) def readchar(self): res = select.select([self.infd], [], [], 0) if res[0]: return self.infile.read(1) else: return None def write(self, buf): self.outfile.write(buf) class ConsoleWindows: KEY_MAP = { b"H": b"A", # UP b"P": b"B", # DOWN b"M": b"C", # RIGHT b"K": b"D", # LEFT b"G": b"H", # POS1 b"O": b"F", # END b"Q": b"6~", # PGDN b"I": b"5~", # PGUP b"s": b"1;5D", # CTRL-LEFT, b"t": b"1;5C", # CTRL-RIGHT, b"\x8d": b"1;5A", # CTRL-UP, b"\x91": b"1;5B", # CTRL-DOWN, b"w": b"1;5H", # CTRL-POS1 b"u": b"1;5F", # CTRL-END b"\x98": b"1;3A", # ALT-UP, b"\xa0": b"1;3B", # ALT-DOWN, b"\x9d": b"1;3C", # ALT-RIGHT, b"\x9b": b"1;3D", # ALT-LEFT, b"\x97": b"1;3H", # ALT-POS1, b"\x9f": b"1;3F", # ALT-END, b"S": b"3~", # DEL, b"\x93": b"3;5~", # CTRL-DEL b"R": b"2~", # INS b"\x92": b"2;5~", # CTRL-INS b"\x94": b"Z", # Ctrl-Tab = BACKTAB, } def __init__(self): self.ctrl_c = 0 def _sigint_handler(self, signo, frame): self.ctrl_c += 1 def enter(self): signal.signal(signal.SIGINT, self._sigint_handler) def exit(self): signal.signal(signal.SIGINT, signal.SIG_DFL) def inWaiting(self): return 1 if self.ctrl_c or msvcrt.kbhit() else 0 def waitchar(self, pyb_serial): while not (self.inWaiting() or pyb_serial.inWaiting()): time.sleep(0.01) def readchar(self): if self.ctrl_c: self.ctrl_c -= 1 return b"\x03" 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 try: ch = b"\x1b[" + self.KEY_MAP[ch] except KeyError: return None 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, os 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1664978292.0 mpremote-0.4.0/mpremote/main.py0000644000175000017500000003760214317306564016026 0ustar00damiendamien""" MicroPython Remote - Interaction and automation tool for MicroPython MIT license; Copyright (c) 2019-2022 Damien P. George This program provides a set of utilities to interact with and automate a MicroPython device over a serial connection. Commands supported are: mpremote -- auto-detect, connect and enter REPL mpremote -- connect to given device mpremote connect -- connect to given device mpremote disconnect -- disconnect current device mpremote mount -- mount local directory on device mpremote eval -- evaluate and print the string mpremote exec -- execute the string mpremote run