|
35 | 35 | # Once the API is stabilised, the idea is that mpremote can be used both |
36 | 36 | # as a command line tool and a library for interacting with devices. |
37 | 37 |
|
38 | | -import ast, io, os, re, struct, sys, time |
| 38 | +import ast, io, os, re, stat, struct, sys, time |
39 | 39 | import serial |
40 | 40 | import serial.tools.list_ports |
41 | 41 | from errno import EPERM |
@@ -105,6 +105,31 @@ def __init__(self, device, baudrate=115200, wait=0, exclusive=True, timeout=None |
105 | 105 | if delayed: |
106 | 106 | print("") |
107 | 107 |
|
| 108 | + # Detect if this is a PTY device (e.g., QEMU serial output) |
| 109 | + # PTY devices don't reliably report inWaiting() status, so we need |
| 110 | + # to use blocking reads instead of checking for data availability. |
| 111 | + self.is_pty = self._is_pty_device(device) |
| 112 | + |
| 113 | + def _is_pty_device(self, device): |
| 114 | + """ |
| 115 | + Detect if device is a PTY (pseudo-terminal). |
| 116 | +
|
| 117 | + PTY devices are commonly used by emulators like QEMU. Unlike real serial |
| 118 | + devices, PTY inWaiting() may not report data availability correctly, |
| 119 | + requiring use of blocking reads instead. |
| 120 | + """ |
| 121 | + try: |
| 122 | + # Linux Unix98 PTY pattern: /dev/pts/N |
| 123 | + if device.startswith("/dev/pts/"): |
| 124 | + st = os.stat(device) |
| 125 | + # Unix98 PTY slaves have major device number 136 on Linux |
| 126 | + if stat.S_ISCHR(st.st_mode) and os.major(st.st_rdev) == 136: |
| 127 | + return True |
| 128 | + except (OSError, AttributeError): |
| 129 | + # If detection fails or os.major not available, assume not a PTY |
| 130 | + pass |
| 131 | + return False |
| 132 | + |
108 | 133 | def close(self): |
109 | 134 | # ESP Windows quirk: Prevent target from resetting when Windows clears DTR before RTS |
110 | 135 | self.serial.rts = False |
@@ -133,22 +158,27 @@ def read_until( |
133 | 158 | while True: |
134 | 159 | if data.endswith(ending): |
135 | 160 | break |
136 | | - elif self.serial.inWaiting() > 0: |
| 161 | + |
| 162 | + # PTY: always read (blocking with timeout), Serial: check inWaiting() first |
| 163 | + if self.is_pty or self.serial.inWaiting() > 0: |
137 | 164 | new_data = self.serial.read(1) |
138 | | - if data_consumer: |
139 | | - data_consumer(new_data) |
140 | | - data = new_data |
141 | | - else: |
142 | | - data = data + new_data |
143 | | - begin_char_s = time.monotonic() |
144 | | - else: |
145 | | - if timeout is not None and time.monotonic() >= begin_char_s + timeout: |
146 | | - break |
147 | | - if ( |
148 | | - timeout_overall is not None |
149 | | - and time.monotonic() >= begin_overall_s + timeout_overall |
150 | | - ): |
151 | | - break |
| 165 | + if new_data: |
| 166 | + if data_consumer: |
| 167 | + data_consumer(new_data) |
| 168 | + data = new_data |
| 169 | + else: |
| 170 | + data = data + new_data |
| 171 | + begin_char_s = time.monotonic() |
| 172 | + |
| 173 | + # Check timeouts (applies to both PTY and real serial) |
| 174 | + if timeout is not None and time.monotonic() >= begin_char_s + timeout: |
| 175 | + break |
| 176 | + if ( |
| 177 | + timeout_overall is not None |
| 178 | + and time.monotonic() >= begin_overall_s + timeout_overall |
| 179 | + ): |
| 180 | + break |
| 181 | + if not self.is_pty: |
152 | 182 | time.sleep(0.01) |
153 | 183 | return data |
154 | 184 |
|
|
0 commit comments