|
| 1 | +import ctypes |
| 2 | +import inspect |
| 3 | +import json |
| 4 | +import sys |
| 5 | +import textwrap |
| 6 | +import threading |
| 7 | +import time |
| 8 | + |
| 9 | +import serial |
| 10 | + |
| 11 | +BUFFER_SIZE = 1024 |
| 12 | + |
| 13 | + |
| 14 | +class RawReplError(BaseException): |
| 15 | + pass |
| 16 | + |
| 17 | + |
| 18 | +class RawRepl(): |
| 19 | + |
| 20 | + def __init__(self, device, baudrate=115200, rawdelay=0): |
| 21 | + super().__init__() |
| 22 | + self._rawdelay = rawdelay |
| 23 | + |
| 24 | + if isinstance(device, str): |
| 25 | + self.repl = serial.Serial(device, baudrate) |
| 26 | + elif isinstance(device, serial.Serial): |
| 27 | + self.repl = device |
| 28 | + else: |
| 29 | + raise RawReplError('can not init RawRepl') |
| 30 | + |
| 31 | + def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None): |
| 32 | + data = self.repl.read(min_num_bytes) |
| 33 | + if data_consumer: |
| 34 | + data_consumer(data) |
| 35 | + timeout_count = 0 |
| 36 | + while True: |
| 37 | + if data.endswith(ending): |
| 38 | + break |
| 39 | + elif self.repl.inWaiting() > 0: |
| 40 | + new_data = self.repl.read(1) |
| 41 | + data = data + new_data |
| 42 | + if data_consumer: |
| 43 | + data_consumer(new_data) |
| 44 | + timeout_count = 0 |
| 45 | + else: |
| 46 | + timeout_count += 1 |
| 47 | + if timeout is not None and timeout_count >= 100 * timeout: |
| 48 | + break |
| 49 | + time.sleep(0.01) |
| 50 | + return data |
| 51 | + |
| 52 | + def enter_raw_repl(self): |
| 53 | + print('==> Entering Raw REPL') |
| 54 | + # Brief delay before sending RAW MODE char if requests |
| 55 | + if self._rawdelay > 0: |
| 56 | + time.sleep(self._rawdelay) |
| 57 | + |
| 58 | + # ctrl-C twice: interrupt any running program |
| 59 | + self.repl.write(b'\r\x03\x03') |
| 60 | + |
| 61 | + # flush input (without relying on serial.flushInput()) |
| 62 | + n = self.repl.inWaiting() |
| 63 | + while n > 0: |
| 64 | + self.repl.read(n) |
| 65 | + n = self.repl.inWaiting() |
| 66 | + time.sleep(2) |
| 67 | + |
| 68 | + self.repl.write(b'\r\x01') # ctrl-A: enter raw REPL |
| 69 | + data = self.read_until(1, b'raw REPL; CTRL-B to exit\r\n>') |
| 70 | + if not data.endswith(b'raw REPL; CTRL-B to exit\r\n>'): |
| 71 | + print(data) |
| 72 | + raise RawReplError('could not enter raw repl') |
| 73 | + print('---> now in rawrepl mode') |
| 74 | + |
| 75 | + def exit_raw_repl(self): |
| 76 | + print('==> Exit Raw REPL') |
| 77 | + self.repl.write(b'\r\x02') # ctrl-B: enter friendly REPL |
| 78 | + |
| 79 | + def follow(self, timeout, data_consumer=None): |
| 80 | + # wait for normal output |
| 81 | + data = self.read_until(1, b'\x04', timeout=timeout, |
| 82 | + data_consumer=data_consumer) |
| 83 | + if not data.endswith(b'\x04'): |
| 84 | + raise RawReplError('timeout waiting for first EOF reception') |
| 85 | + data = data[:-1] |
| 86 | + |
| 87 | + # wait for error output |
| 88 | + data_err = self.read_until(1, b'\x04', timeout=timeout) |
| 89 | + if not data_err.endswith(b'\x04'): |
| 90 | + raise RawReplError('timeout waiting for second EOF reception') |
| 91 | + data_err = data_err[:-1] |
| 92 | + |
| 93 | + # return normal and error output |
| 94 | + return data, data_err |
| 95 | + |
| 96 | + def exec_raw_no_follow(self, command): |
| 97 | + if isinstance(command, bytes): |
| 98 | + command_bytes = command |
| 99 | + else: |
| 100 | + command_bytes = bytes(command, encoding='utf8') |
| 101 | + |
| 102 | + # check we have a prompt |
| 103 | + # data = self.read_until(1, b'>') |
| 104 | + # if not data.endswith(b'>'): |
| 105 | + # raise RawReplError('could not enter raw repl') |
| 106 | + |
| 107 | + # write command |
| 108 | + for i in range(0, len(command_bytes), 256): |
| 109 | + self.repl.write( |
| 110 | + command_bytes[i:min(i + 256, len(command_bytes))]) |
| 111 | + time.sleep(0.01) |
| 112 | + self.repl.write(b'\x04') |
| 113 | + |
| 114 | + # check if we could exec command |
| 115 | + # data = self.repl.read(2) |
| 116 | + # if data != b'OK': |
| 117 | + # raise RawReplError('could not exec command') |
| 118 | + |
| 119 | + def exec_raw(self, command, timeout=10, data_consumer=None): |
| 120 | + self.exec_raw_no_follow(command) |
| 121 | + return self.follow(timeout, data_consumer) |
| 122 | + |
| 123 | + def eval(self, expression): |
| 124 | + ret = self.exec__('print({})'.format(expression)) |
| 125 | + ret = ret.strip() |
| 126 | + return ret |
| 127 | + |
| 128 | + def exec__(self, command): |
| 129 | + ret, ret_err = self.exec_raw(command) |
| 130 | + if ret_err: |
| 131 | + raise RawReplError('exception', ret, ret_err) |
| 132 | + return ret |
| 133 | + |
| 134 | + def execfile(self, filename): |
| 135 | + with open(filename, 'rb') as f: |
| 136 | + pyfile = f.read() |
| 137 | + return self.exec__(pyfile) |
| 138 | + |
| 139 | + def get_file(self, filename): |
| 140 | + """Retrieve the contents of the specified file and return its contents |
| 141 | + as a byte string. |
| 142 | + """ |
| 143 | + # Open the file and read it a few bytes at a time and print out the |
| 144 | + # raw bytes. Be careful not to overload the UART buffer so only write |
| 145 | + # a few bytes at a time, and don't use print since it adds newlines and |
| 146 | + # expects string data. |
| 147 | + command = """ |
| 148 | + import sys |
| 149 | + with open('{0}', 'rb') as infile: |
| 150 | + while True: |
| 151 | + result = infile.read({1}) |
| 152 | + if result == b'': |
| 153 | + break |
| 154 | + len = sys.stdout.write(result) |
| 155 | + """.format( |
| 156 | + filename, BUFFER_SIZE |
| 157 | + ) |
| 158 | + self.enter_raw_repl() |
| 159 | + try: |
| 160 | + out = self.exec__(textwrap.dedent(command)) |
| 161 | + except RawReplError as ex: |
| 162 | + # Check if this is an OSError #2, i.e. file doesn't exist and |
| 163 | + # rethrow it as something more descriptive. |
| 164 | + if ex.args[2].decode("utf-8").find("OSError: [Errno 2] ENOENT") != -1: |
| 165 | + raise RuntimeError("No such file: {0}".format(filename)) |
| 166 | + else: |
| 167 | + raise ex |
| 168 | + self.exit_raw_repl() |
| 169 | + return out[2::] |
| 170 | + |
| 171 | + def put_file(self, filename, data): |
| 172 | + """Create or update the specified file with the provided data. |
| 173 | + """ |
| 174 | + |
| 175 | + data = data.encode('utf-8') |
| 176 | + # Open the file for writing on the board and write chunks of data. |
| 177 | + # self.enter_raw_repl() |
| 178 | + self.exec__("f = open('{0}', 'wb')".format(filename)) |
| 179 | + size = len(data) |
| 180 | + # Loop through and write a buffer size chunk of data at a time. |
| 181 | + for i in range(0, size, BUFFER_SIZE): |
| 182 | + chunk_size = min(BUFFER_SIZE, size - i) |
| 183 | + chunk = repr(data[i: i + chunk_size]) |
| 184 | + # Make sure to send explicit byte strings (handles python 2 compatibility). |
| 185 | + if not chunk.startswith("b"): |
| 186 | + chunk = "b" + chunk |
| 187 | + self.exec__("f.write({0})".format(chunk)) |
| 188 | + self.exec__("f.close()") |
| 189 | + self.exit_raw_repl() |
0 commit comments