Skip to content

Commit c5a5fe9

Browse files
Improve instrumentation
1 parent d378098 commit c5a5fe9

1 file changed

Lines changed: 92 additions & 16 deletions

File tree

scripts/mpos_controller.py

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -156,36 +156,58 @@ def read_until(self, ending, timeout=30):
156156
data += chunk
157157
return data
158158

159+
def _try_get_prompt(self, action, wait=0.5, drain_before=True):
160+
"""Send *action* bytes, then wait briefly for ``>>> ``."""
161+
if drain_before:
162+
self._drain(0.3)
163+
self.stream.write(action)
164+
time.sleep(wait)
165+
data = self._drain(1.0)
166+
if b">>> " in data:
167+
return True
168+
return False
169+
159170
def wait_for_boot(self, timeout=30):
160171
t0 = time.monotonic()
161172
data = b""
173+
# 1. Try ENTER first — device may already be at a prompt
174+
if self._try_get_prompt(b"\r\n", wait=0.5):
175+
return
176+
# 2. Try Ctrl-B to exit raw REPL
177+
if self._try_get_prompt(b"\x02", wait=0.5):
178+
self.stream.write(b"\r\n")
179+
time.sleep(0.2)
180+
self._drain(0.3)
181+
return
182+
# 3. Main loop: poll for data, send Ctrl-C after 2s of silence
162183
ctrl_c_sent = False
163184
while time.monotonic() - t0 < timeout:
164185
if self._data_waiting(0.1):
165186
chunk = self.stream.read(4096)
166187
if chunk:
167188
data += chunk
168189
if b">>> " in data:
169-
self._drain(0.5)
170-
self.stream.write(b"\n")
171-
time.sleep(0.2)
172190
self._drain(0.3)
173191
return
174192
elif not ctrl_c_sent and time.monotonic() - t0 > 2:
175193
self.stream.write(b"\x03")
176194
time.sleep(0.3)
177195
ctrl_c_sent = True
178-
self.stream.write(b"\x02")
179-
time.sleep(0.2)
180-
self._drain(0.3)
181-
data = self.read_until(b">>> ", timeout=5)
182-
if not data.endswith(b">>> "):
183-
raise TimeoutError(
184-
"aioREPL prompt not found.\n" + data.decode("utf-8", "replace")[-2000:]
185-
)
186-
self.stream.write(b"\n")
187-
time.sleep(0.3)
188-
self._drain(0.5)
196+
# 4. Fallback: drain, try Ctrl-B, then do one last read
197+
tail = self._drain(1.0)
198+
data += tail
199+
if self._try_get_prompt(b"\x02", wait=0.5, drain_before=False):
200+
return
201+
data += self.read_until(b">>> ", timeout=5)
202+
if b">>> " in data:
203+
self._drain(0.3)
204+
return
205+
leftover = self._drain(2.0)
206+
data += leftover
207+
raise TimeoutError(
208+
"aioREPL prompt not found.\n"
209+
+ data.decode("utf-8", "replace")[-3000:]
210+
)
189211

190212
def exec(self, code, timeout=30):
191213
"""
@@ -340,9 +362,24 @@ def exec_multiline(self, code):
340362
def eval(self, expr):
341363
return self.repl.eval(expr)
342364

365+
# -- disk space ----------------------------------------------------
366+
367+
def check_free_space(self):
368+
raw = self.exec(
369+
"import os; fs=os.statvfs('/'); print(fs[0]*fs[3])"
370+
)
371+
return int(raw.strip().decode("utf-8"))
372+
343373
# -- screen capture ------------------------------------------------
344374

345375
def screenshot(self):
376+
free = self.check_free_space()
377+
needed = self._width * self._height * 3
378+
if free < needed:
379+
raise RuntimeError(
380+
"Insufficient free space for screenshot: "
381+
"{} bytes free, need at least {} bytes".format(free, needed)
382+
)
346383
tmp = "/tmp/_mpos_shot.raw"
347384
code = (
348385
"import lvgl as lv; "
@@ -518,7 +555,20 @@ def exec_multiline(self, code):
518555
def eval(self, expr):
519556
return self.repl.eval(expr)
520557

558+
def check_free_space(self):
559+
raw = self.exec(
560+
"import os; fs=os.statvfs('/'); print(fs[0]*fs[3])"
561+
)
562+
return int(raw.strip().decode("utf-8"))
563+
521564
def screenshot(self):
565+
free = self.check_free_space()
566+
needed = self._width * self._height * 3
567+
if free < needed:
568+
raise RuntimeError(
569+
"Insufficient free space for screenshot on device: "
570+
"{} bytes free, need at least {} bytes".format(free, needed)
571+
)
522572
tmp = "/_mpos_shot.raw"
523573
code = (
524574
"import lvgl as lv; "
@@ -675,6 +725,9 @@ def eval(self, expr):
675725
def screenshot(self):
676726
return self._backend.screenshot()
677727

728+
def check_free_space(self):
729+
return self._backend.check_free_space()
730+
678731
def press(self, x, y):
679732
self._backend.press(x, y)
680733

@@ -715,7 +768,7 @@ def main():
715768
parser = argparse.ArgumentParser(description="MicroPythonOS Controller")
716769
parser.add_argument(
717770
"action", nargs="?", default="exec",
718-
help="Action: exec, eval, screenshot (default: exec)",
771+
help="Action: exec, eval, screenshot, startapp, checkfreespace (default: exec)",
719772
)
720773
parser.add_argument("args", nargs="*", help="Arguments")
721774
parser.add_argument("--binary", help="Path to lvgl_micropy_unix binary")
@@ -724,11 +777,16 @@ def main():
724777
"--serial-port", help="Serial port for device (e.g. /dev/ttyACM0)"
725778
)
726779
parser.add_argument("--baudrate", type=int, default=115200)
780+
parser.add_argument(
781+
"--no-reset", action="store_true",
782+
help="Skip DTR/RTS reset on serial connect (device already running)"
783+
)
727784
args = parser.parse_args()
728785

729786
if args.serial_port:
730787
ctrl = MPOSController(
731-
backend="serial", port=args.serial_port, baudrate=args.baudrate
788+
backend="serial", port=args.serial_port,
789+
baudrate=args.baudrate, reset=not args.no_reset
732790
)
733791
else:
734792
ctrl = MPOSController(binary=args.binary, heapsize=args.heapsize)
@@ -761,6 +819,24 @@ def main():
761819
with open(path, "wb") as f:
762820
f.write(bmp)
763821
print("Wrote", path, "({} bytes)".format(len(bmp)))
822+
elif args.action == "startapp":
823+
if not args.args:
824+
print("error: app name required", file=sys.stderr)
825+
return 1
826+
appname = args.args[0]
827+
with ctrl:
828+
out = ctrl.exec("from mpos import AppManager ; AppManager.start_app({!r})".format(appname))
829+
sys.stdout.buffer.write(out)
830+
sys.stdout.buffer.write(b"\n")
831+
elif args.action == "checkfreespace":
832+
with ctrl:
833+
free = ctrl.check_free_space()
834+
needed = ctrl.display_size[0] * ctrl.display_size[1] * 3
835+
print("Free: {} bytes, need for screenshot: {} bytes".format(free, needed))
836+
if free < needed:
837+
print("WARNING: not enough space for a screenshot!")
838+
else:
839+
print("OK")
764840
else:
765841
parser.print_help()
766842
return 1

0 commit comments

Comments
 (0)