import io, os, re, serial, struct, time from errno import EPERM from .console import VT_ENABLED try: from .pyboard import Pyboard, PyboardError, stdout_write_bytes, filesystem_command except ImportError: import sys sys.path.append(os.path.dirname(__file__) + "/../..") from pyboard import Pyboard, PyboardError, stdout_write_bytes, filesystem_command fs_hook_cmds = { "CMD_STAT": 1, "CMD_ILISTDIR_START": 2, "CMD_ILISTDIR_NEXT": 3, "CMD_OPEN": 4, "CMD_CLOSE": 5, "CMD_READ": 6, "CMD_WRITE": 7, "CMD_SEEK": 8, "CMD_REMOVE": 9, "CMD_RENAME": 10, "CMD_MKDIR": 11, "CMD_RMDIR": 12, } fs_hook_code = """\ import uos, uio, ustruct, micropython SEEK_SET = 0 class RemoteCommand: def __init__(self): import uselect, usys self.buf4 = bytearray(4) self.fout = usys.stdout.buffer self.fin = usys.stdin.buffer self.poller = uselect.poll() self.poller.register(self.fin, uselect.POLLIN) def poll_in(self): for _ in self.poller.ipoll(1000): return self.end() raise Exception('timeout waiting for remote') def rd(self, n): buf = bytearray(n) self.rd_into(buf, n) return buf def rd_into(self, buf, n): # implement reading with a timeout in case other side disappears if n == 0: return self.poll_in() r = self.fin.readinto(buf, n) if r < n: mv = memoryview(buf) while r < n: self.poll_in() r += self.fin.readinto(mv[r:], n - r) def begin(self, type): micropython.kbd_intr(-1) buf4 = self.buf4 buf4[0] = 0x18 buf4[1] = type self.fout.write(buf4, 2) # Wait for sync byte 0x18, but don't get stuck forever for i in range(30): self.poller.poll(1000) self.fin.readinto(buf4, 1) if buf4[0] == 0x18: break def end(self): micropython.kbd_intr(3) def rd_s8(self): self.rd_into(self.buf4, 1) n = self.buf4[0] if n & 0x80: n -= 0x100 return n def rd_s32(self): buf4 = self.buf4 self.rd_into(buf4, 4) n = buf4[0] | buf4[1] << 8 | buf4[2] << 16 | buf4[3] << 24 if buf4[3] & 0x80: n -= 0x100000000 return n def rd_u32(self): buf4 = self.buf4 self.rd_into(buf4, 4) return buf4[0] | buf4[1] << 8 | buf4[2] << 16 | buf4[3] << 24 def rd_bytes(self, buf): # TODO if n is large (eg >256) then we may miss bytes on stdin n = self.rd_s32() if buf is None: ret = buf = bytearray(n) else: ret = n self.rd_into(buf, n) return ret def rd_str(self): n = self.rd_s32() if n == 0: return '' else: return str(self.rd(n), 'utf8') def wr_s8(self, i): self.buf4[0] = i self.fout.write(self.buf4, 1) def wr_s32(self, i): ustruct.pack_into(' {len(buf)}") def do_seek(self): fd = self.rd_s8() n = self.rd_s32() whence = self.rd_s8() # self.log_cmd(f"seek {fd} {n}") try: n = self.data_files[fd][0].seek(n, whence) except io.UnsupportedOperation: n = -1 self.wr_s32(n) def do_write(self): fd = self.rd_s8() buf = self.rd_bytes() if self.data_files[fd][1]: buf = str(buf, "utf8") n = self.data_files[fd][0].write(buf) self.wr_s32(n) # self.log_cmd(f"write {fd} {len(buf)} -> {n}") def do_remove(self): path = self.root + self.rd_str() # self.log_cmd(f"remove {path}") try: self.path_check(path) os.remove(path) ret = 0 except OSError as er: ret = -abs(er.errno) self.wr_s32(ret) def do_rename(self): old = self.root + self.rd_str() new = self.root + self.rd_str() # self.log_cmd(f"rename {old} {new}") try: self.path_check(old) self.path_check(new) os.rename(old, new) ret = 0 except OSError as er: ret = -abs(er.errno) self.wr_s32(ret) def do_mkdir(self): path = self.root + self.rd_str() # self.log_cmd(f"mkdir {path}") try: self.path_check(path) os.mkdir(path) ret = 0 except OSError as er: ret = -abs(er.errno) self.wr_s32(ret) def do_rmdir(self): path = self.root + self.rd_str() # self.log_cmd(f"rmdir {path}") try: self.path_check(path) os.rmdir(path) ret = 0 except OSError as er: ret = -abs(er.errno) self.wr_s32(ret) cmd_table = { fs_hook_cmds["CMD_STAT"]: do_stat, fs_hook_cmds["CMD_ILISTDIR_START"]: do_ilistdir_start, fs_hook_cmds["CMD_ILISTDIR_NEXT"]: do_ilistdir_next, fs_hook_cmds["CMD_OPEN"]: do_open, fs_hook_cmds["CMD_CLOSE"]: do_close, fs_hook_cmds["CMD_READ"]: do_read, fs_hook_cmds["CMD_WRITE"]: do_write, fs_hook_cmds["CMD_SEEK"]: do_seek, fs_hook_cmds["CMD_REMOVE"]: do_remove, fs_hook_cmds["CMD_RENAME"]: do_rename, fs_hook_cmds["CMD_MKDIR"]: do_mkdir, fs_hook_cmds["CMD_RMDIR"]: do_rmdir, } class SerialIntercept: def __init__(self, serial, cmd): self.orig_serial = serial self.cmd = cmd self.buf = b"" self.orig_serial.timeout = 5.0 def _check_input(self, blocking): if blocking or self.orig_serial.inWaiting() > 0: c = self.orig_serial.read(1) if c == b"\x18": # a special command c = self.orig_serial.read(1)[0] self.orig_serial.write(b"\x18") # Acknowledge command PyboardCommand.cmd_table[c](self.cmd) elif not VT_ENABLED and c == b"\x1b": # ESC code, ignore these on windows esctype = self.orig_serial.read(1) if esctype == b"[": # CSI while not (0x40 < self.orig_serial.read(1)[0] < 0x7E): # Looking for "final byte" of escape sequence pass else: self.buf += c @property def fd(self): return self.orig_serial.fd def close(self): self.orig_serial.close() def inWaiting(self): self._check_input(False) return len(self.buf) def read(self, n): while len(self.buf) < n: self._check_input(True) out = self.buf[:n] self.buf = self.buf[n:] return out def write(self, buf): self.orig_serial.write(buf) class PyboardExtended(Pyboard): def __init__(self, dev, *args, **kwargs): super().__init__(dev, *args, **kwargs) self.device_name = dev self.mounted = False def mount_local(self, path): fout = self.serial if self.eval('"RemoteFS" in globals()') == b"False": self.exec_(fs_hook_code) self.exec_("__mount()") self.mounted = True self.cmd = PyboardCommand(self.serial, fout, path) self.serial = SerialIntercept(self.serial, self.cmd) def write_ctrl_d(self, out_callback): self.serial.write(b"\x04") if not self.mounted: return # Read response from the device until it is quiet (with a timeout). INITIAL_TIMEOUT = 0.5 QUIET_TIMEOUT = 0.2 FULL_TIMEOUT = 5 t_start = t_last_activity = time.monotonic() data_all = b"" while True: t = time.monotonic() n = self.serial.inWaiting() if n > 0: data = self.serial.read(n) out_callback(data) data_all += data t_last_activity = t else: if len(data_all) == 0: if t - t_start > INITIAL_TIMEOUT: return else: if t - t_start > FULL_TIMEOUT: return if t - t_last_activity > QUIET_TIMEOUT: break # Check if a soft reset occurred. if data_all.find(b"MPY: soft reboot") == -1: return if data_all.endswith(b">>> "): in_friendly_repl = True elif data_all.endswith(b">"): in_friendly_repl = False else: return # Clear state while board remounts, it will be re-set once mounted. self.mounted = False self.serial = self.serial.orig_serial # Provide a message about the remount. out_callback(bytes(f"\r\nRemount local directory {self.cmd.root} at /remote\r\n", "utf8")) # Enter raw REPL and re-mount the remote filesystem. self.serial.write(b"\x01") self.exec_(fs_hook_code) self.exec_("__mount()") self.mounted = True # Exit raw REPL if needed, and wait for the friendly REPL prompt. if in_friendly_repl: self.exit_raw_repl() prompt = b">>> " else: prompt = b">" self.read_until(len(prompt), prompt) out_callback(prompt) self.serial = SerialIntercept(self.serial, self.cmd) def umount_local(self): if self.mounted: self.exec_('uos.umount("/remote")') self.mounted = False self.serial = self.serial.orig_serial