def make_dir(devs, dst_dir, dry_run, recursed): """Creates a directory. Produces information in case of dry run. Isues error where necessary. """ parent = os.path.split(dst_dir.rstrip( '/'))[0] # Check for nonexistent parent parent_files = auto(devs, listdir_stat, parent) if parent else True # Relative dir if dry_run: if recursed: # Assume success: parent not actually created yet qprint("Creating directory {}".format(dst_dir)) elif parent_files is None: qprint("Unable to create {}".format(dst_dir)) return True if not mkdir(devs, dst_dir): eprint("Unable to create {}".format(dst_dir)) return False return True
def do_rsync(self, line): """rsync [-m|--mirror] [-n|--dry-run] [SRC_DIR [DST_DIR]] Synchronize destination directory tree to source directory tree. """ db = self.boards.default args = self.line_to_args(line) sd = args.src_dst_dir if len(sd) > 2: eprint("*** More than one destination directory given") return src_dir = sd[0] if len(sd) > 0 else db.get_config('host_dir', '~/iot49') dst_dir = sd[1] if len(sd) > 1 else db.get_config('remote_dir', '/flash') src_dir = resolve_path(self.cur_dir, src_dir) dst_dir = resolve_path(self.cur_dir, dst_dir) if len(sd) < 2: qprint("synchronizing {} --> {}".format(src_dir, dst_dir)) rsync(self.boards, src_dir, dst_dir, mirror=not args.mirror, dry_run=args.dry_run, recursed=True)
def __init__(self, port, baudrate): try: # wait for port to come online for wait in range(3): if os.path.exists(port): break qprint("Waiting for port '{}' to come online".format(port)) time.sleep(1) # try to connect for attempt in range(5): try: self._serial = Serial(port, baudrate, inter_byte_timeout=1) break except IOError: qprint("Waiting for serial connection at '{}'".format(port)) time.sleep(1) # send Control-C to put MicroPython in known state for attempt in range(20): try: self._serial.write(b'\x03') break except SerialException: time.sleep(0.5) qprint("Trying to talk to the MicroPython interpreter") self._port = port except AttributeError: raise ConnectionError("Failed connecting to board at '{}'".format(port)) except KeyboardInterrupt: self._serial = None
def _esp_flasher(self, version, **kwargs): """Flash firmware""" # flash command cmd = "esptool.py --port {} --baud {} {} {}".format( kwargs['port'], kwargs['baudrate'], kwargs['flash_options'], ' '.join(["0x{:x} {}".format(addr, file) for addr, file in self.spec['partitions']]) ) try: with TemporaryDirectory() as dir: os.chdir(dir) # download firmware for p in self.spec['partitions']: url = self.url + self.board + '/' + version + '/' + p[1] qprint("download", url) urlretrieve(url, p[1]) # flash qprint("flashing ...", cmd) self._esptool(cmd) except PermissionError: # Windows throws error pass
def erase_flash(self, port): qprint("erasing flash ...") cmd = "esptool.py --port {} erase_flash".format(port) self._esptool(cmd)
def _board_characteristics(self): """Get device id and other updates""" # get unique board id self._id = self.remote_eval(get_unique_id, 'BOARD HAS NO ID') qprint("Connected to '{}' (id={}) ...".format(self.name, self.id), end='', flush=True) # check buffer self._has_buffer = self.remote_eval(test_buffer) qprint(" has_buffer={}".format(self._has_buffer), end='', flush=True) if self._serial.is_circuit_python: qprint() else: # get root dirs qprint("{} dirs=".format(self._has_buffer), end='', flush=True) self._root_dirs = [ '/{}/'.format(dir) for dir in self.remote_eval(listroot) ] qprint(self._root_dirs, end='', flush=True) if not self.get_config('mac'): qprint(" mac=", end='', flush=True) self.set_config('mac', self.remote_eval(get_mac_address)) qprint(self.get_config('mac'), end='', flush=True) # sync time now = time.localtime(time.time()) qprint(" sync time ...") self.remote(set_time, now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec) qprint()
def __init__(self, port=None, baudrate=115200): self.is_circuitpy = False try: # check which ports are available if not port: for p in comports(): if p.vid == ADAFRUIT_VID: port = p.device self.is_circuitpy = True break elif p.vid == ESP32_VID: port = p.device break elif p.vid: qprint(f"Unknown board {p} with vid '{p.vid}' skipped") # did we find a valid board? if not port: eprint("No board found") sys.exit(1) # wait for port to come online for wait in range(3): if os.path.exists(port): break qprint("Waiting for port '{}' to come online".format(port)) time.sleep(1) # try to connect for attempt in range(5): try: self._serial = Serial(port, baudrate, parity='N', inter_byte_timeout=1) break except IOError as e: qprint( "Waiting for serial connection at '{}'".format(port)) qprint(e) time.sleep(1) # send Control-C to put MicroPython in known state for attempt in range(20): try: self._serial.write(b'\x03') break except SerialException: time.sleep(0.5) qprint("Trying to talk to the MicroPython interpreter") self._port = port qprint(f"SerialConnection to {port} established") except AttributeError: raise ConnectionError( "Failed connecting to board at '{}'".format(port)) except KeyboardInterrupt: self._serial = None
def __exit__(self, exc_type, exc_val, exc_tb): if self._modified: qprint("updating '{}'".format(self._config_file)) self.save()
def do_EOF(self, line): """Type Control-D to quit.""" qprint("Bye") return True
def rsync(devs, src_dir, dst_dir, mirror, dry_run, recursed): """Synchronizes 2 directory trees.""" # This test is a hack to avoid errors when accessing /flash. When the # cache synchronisation issue is solved it should be removed if not isinstance(src_dir, str) or not len(src_dir): return # check that source is a directory sstat = auto(devs, get_stat, src_dir) if not is_dir(sstat): eprint("*** Source {} is not a directory".format(src_dir)) return # create destination directory if it does not exist sstat = auto(devs, get_stat, dst_dir) if not file_exists(sstat): qprint("Create {} on remote".format(dst_dir)) if not dry_run: if recursed and not make_dir(devs, dst_dir, dry_run, recursed): eprint("*** Unable to create directory", dst_dir) elif not is_dir(sstat): eprint("*** Destination {} is not a directory".format(src_dir)) return # get list of src & dst files and stats qprint(" checking {}".format(dst_dir)) d_src = file_dir(devs, src_dir) d_dst = file_dir(devs, dst_dir) # determine what needs to be copied or deleted set_dst = set(d_dst.keys()) set_src = set(d_src.keys()) to_add = set_src - set_dst # Files to copy to dest to_del = set_dst - set_src # To delete from dest to_upd = set_dst.intersection(set_src) # In both: may need updating if False: eprint("rsync {} -> {}".format(src_dir, dst_dir)) eprint(" sources", set_src) eprint(" dest ", set_dst) eprint(" add ", to_add) eprint(" delete ", to_del) eprint(" update ", to_upd) # add ... for f in to_add: src = os.path.join(src_dir, f) dst = os.path.join(dst_dir, f) qprint("Adding {}".format(dst)) if is_dir(d_src[f]): if recursed: rsync(devs, src, dst, mirror, dry_run, recursed) else: if not dry_run: if not cp(devs, src, dst): eprint("*** Unable to add {} --> {}".format(src, dst)) # delete ... for f in to_del: if not mirror: break dst = os.path.join(dst_dir, f) qprint("Removing {}".format(dst)) if not dry_run: res = rm(devs, dst, recursive=True, force=True) if not res: eprint("Cannot remove {}", dst) # update ... for f in to_upd: src = os.path.join(src_dir, f) dst = os.path.join(dst_dir, f) if is_dir(d_src[f]): if is_dir(d_dst[f]): # src and dst are directories if recursed: rsync(devs, src, dst, mirror, dry_run, recursed) else: msg = "Source '{}' is a directory and destination " \ "'{}' is a file. Ignoring" eprint(msg.format(src, dst)) else: if is_dir(d_dst[f]): msg = "Source '{}' is a file and destination " \ "'{}' is a directory. Ignoring" eprint(msg.format(src, dst)) else: if False: eprint("BEB src {} > dst {} delta={}".format( stat_mtime(d_src[f]), stat_mtime(d_dst[f]), stat_mtime(d_src[f]) - stat_mtime(d_dst[f]))) if stat_size(d_src[f]) != stat_size(d_dst[f]) or \ stat_mtime(d_src[f]) > stat_mtime(d_dst[f]): msg = "Copying {} (newer than {})" qprint(msg.format(src, dst)) if not dry_run: if not cp(devs, src, dst): eprint( "*** Unable to update {} --> {}".format(src, dst)) else: dprint(f, "NO update src time:", stat_mtime(d_src[f]), "dst time", stat_mtime( d_dst[f]), "delta", stat_mtime(d_src[f]) - stat_mtime(d_dst[f]))
def main(): """The main program.""" if sys.version_info.major < 3: v = sys.version_info eprint("Shell49 requires Python 3.6 (not {}.{}.{})".format( v.major, v.minor, v.micro)) return default_config = os.getenv('SHELL49_CONFIG_FILE') or '~/.shell49_rc.py' default_editor = os.getenv('SHELL49_EDITOR') or os.getenv( 'VISUAL') or os.getenv('EDITOR') or 'vi' default_nocolor = 'win32' in sys.platform default_debug = False default_quiet = False parser = argparse.ArgumentParser( prog="shell49", usage="%(prog)s [options] [cmd]", description="Remote Shell for MicroPython boards.", epilog=(""" Environment variables: SHELL49_CONFIG_FILE configuration file (Default: '{}') SHELL49_EDITOR editor (Default: {}) """.format(default_config, default_editor)), formatter_class=argparse.RawTextHelpFormatter) parser.add_argument( "-c", "--config", dest="config", help="Set path of the configuration file (default: '%s')" % default_config, default=default_config) parser.add_argument("-e", "--editor", dest="editor", help="Set the editor to use (default: '%s')" % default_editor, default=default_editor) parser.add_argument("-d", "--debug", dest="debug", action="store_true", help="Enable debug features (default %s)" % default_debug, default=default_debug) parser.add_argument("-n", "--nocolor", dest="nocolor", action="store_true", help="Turn off colorized output (default: %s)" % default_nocolor, default=default_nocolor) parser.add_argument("--quiet", dest="quiet", action="store_true", help="Turn off some output (default: %s)" % default_quiet, default=False) parser.add_argument( "-a", "--no_auto_connect", dest="auto_connect", action="store_false", help="Do not automatically connect to board connected to serial port", default=True) parser.add_argument('-V', '--version', dest='version', action='store_true', help='Report the version and exit.', default=False) parser.add_argument("-f", "--file", dest="filename", help="File of commands to process (non-interactive).") parser.add_argument("cmd", nargs=argparse.REMAINDER, help="Optional command to execute and quit.") args = parser.parse_args(sys.argv[1:]) debug(args.debug) quiet(args.quiet or args.cmd or args.filename) if args.nocolor: nocolor() dprint("config = %s" % args.config) dprint("editor = %s" % args.editor) dprint("debug = %s" % args.debug) dprint("quiet = %s" % args.quiet) dprint("nocolor = %s" % args.nocolor) dprint("auto_connect = %s" % args.auto_connect) dprint("version = %s" % __version__) dprint("cmd = [%s]" % ', '.join(args.cmd)) if args.version: print(__version__) return cmd_line = ' '.join(args.cmd) if not args.filename and cmd_line == '': oprint( "Welcome to shell49 version {}. Type 'help' for information; Control-D to exit." .format(__version__)) args.config = os.path.expanduser(args.config) args.config = os.path.normpath(args.config) with Config(args.config) as config: boards = ActiveBoards(config) # connect to board ... try: if args.auto_connect: boards.connect_serial(config.get('default', 'port')) except (ConnectionError, BoardError) as err: eprint(err) except KeyboardInterrupt: pass # start command shell attach_commands() if args.filename: with open(args.filename) as cmd_file: shell = Shell(boards, args.editor, stdin=cmd_file) shell.cmdloop('') else: if boards.num_boards() == 0: eprint( "No MicroPython boards connected - use the connect command to add one." ) shell = Shell(boards, args.editor) try: shell.cmdloop(cmd_line) except KeyboardInterrupt: qprint("Bye") print(printing.NO_COLOR)