pyboard.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. #!/usr/bin/env python
  2. #
  3. # This file is part of the MicroPython project, http://micropython.org/
  4. #
  5. # The MIT License (MIT)
  6. #
  7. # Copyright (c) 2014-2021 Damien P. George
  8. # Copyright (c) 2017 Paul Sokolovsky
  9. #
  10. # Permission is hereby granted, free of charge, to any person obtaining a copy
  11. # of this software and associated documentation files (the "Software"), to deal
  12. # in the Software without restriction, including without limitation the rights
  13. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  14. # copies of the Software, and to permit persons to whom the Software is
  15. # furnished to do so, subject to the following conditions:
  16. #
  17. # The above copyright notice and this permission notice shall be included in
  18. # all copies or substantial portions of the Software.
  19. #
  20. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  23. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  25. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  26. # THE SOFTWARE.
  27. """
  28. pyboard interface
  29. This module provides the Pyboard class, used to communicate with and
  30. control a MicroPython device over a communication channel. Both real
  31. boards and emulated devices (e.g. running in QEMU) are supported.
  32. Various communication channels are supported, including a serial
  33. connection, telnet-style network connection, external process
  34. connection.
  35. Example usage:
  36. import pyboard
  37. pyb = pyboard.Pyboard('/dev/ttyACM0')
  38. Or:
  39. pyb = pyboard.Pyboard('192.168.1.1')
  40. Then:
  41. pyb.enter_raw_repl()
  42. pyb.exec('import pyb')
  43. pyb.exec('pyb.LED(1).on()')
  44. pyb.exit_raw_repl()
  45. Note: if using Python2 then pyb.exec must be written as pyb.exec_.
  46. To run a script from the local machine on the board and print out the results:
  47. import pyboard
  48. pyboard.execfile('test.py', device='/dev/ttyACM0')
  49. This script can also be run directly. To execute a local script, use:
  50. ./pyboard.py test.py
  51. Or:
  52. python pyboard.py test.py
  53. """
  54. import sys
  55. import time
  56. import os
  57. import ast
  58. try:
  59. stdout = sys.stdout.buffer
  60. except AttributeError:
  61. # Python2 doesn't have buffer attr
  62. stdout = sys.stdout
  63. def stdout_write_bytes(b):
  64. b = b.replace(b"\x04", b"")
  65. stdout.write(b)
  66. stdout.flush()
  67. class PyboardError(Exception):
  68. pass
  69. class TelnetToSerial:
  70. def __init__(self, ip, user, password, read_timeout=None):
  71. self.tn = None
  72. import telnetlib
  73. self.tn = telnetlib.Telnet(ip, timeout=15)
  74. self.read_timeout = read_timeout
  75. if b"Login as:" in self.tn.read_until(b"Login as:", timeout=read_timeout):
  76. self.tn.write(bytes(user, "ascii") + b"\r\n")
  77. if b"Password:" in self.tn.read_until(b"Password:", timeout=read_timeout):
  78. # needed because of internal implementation details of the telnet server
  79. time.sleep(0.2)
  80. self.tn.write(bytes(password, "ascii") + b"\r\n")
  81. if b"for more information." in self.tn.read_until(
  82. b'Type "help()" for more information.', timeout=read_timeout
  83. ):
  84. # login successful
  85. from collections import deque
  86. self.fifo = deque()
  87. return
  88. raise PyboardError("Failed to establish a telnet connection with the board")
  89. def __del__(self):
  90. self.close()
  91. def close(self):
  92. if self.tn:
  93. self.tn.close()
  94. def read(self, size=1):
  95. while len(self.fifo) < size:
  96. timeout_count = 0
  97. data = self.tn.read_eager()
  98. if len(data):
  99. self.fifo.extend(data)
  100. timeout_count = 0
  101. else:
  102. time.sleep(0.25)
  103. if self.read_timeout is not None and timeout_count > 4 * self.read_timeout:
  104. break
  105. timeout_count += 1
  106. data = b""
  107. while len(data) < size and len(self.fifo) > 0:
  108. data += bytes([self.fifo.popleft()])
  109. return data
  110. def write(self, data):
  111. self.tn.write(data)
  112. return len(data)
  113. def inWaiting(self):
  114. n_waiting = len(self.fifo)
  115. if not n_waiting:
  116. data = self.tn.read_eager()
  117. self.fifo.extend(data)
  118. return len(data)
  119. else:
  120. return n_waiting
  121. class ProcessToSerial:
  122. "Execute a process and emulate serial connection using its stdin/stdout."
  123. def __init__(self, cmd):
  124. import subprocess
  125. self.subp = subprocess.Popen(
  126. cmd,
  127. bufsize=0,
  128. shell=True,
  129. preexec_fn=os.setsid,
  130. stdin=subprocess.PIPE,
  131. stdout=subprocess.PIPE,
  132. )
  133. # Initially was implemented with selectors, but that adds Python3
  134. # dependency. However, there can be race conditions communicating
  135. # with a particular child process (like QEMU), and selectors may
  136. # still work better in that case, so left inplace for now.
  137. #
  138. # import selectors
  139. # self.sel = selectors.DefaultSelector()
  140. # self.sel.register(self.subp.stdout, selectors.EVENT_READ)
  141. import select
  142. self.poll = select.poll()
  143. self.poll.register(self.subp.stdout.fileno())
  144. def close(self):
  145. import signal
  146. os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
  147. def read(self, size=1):
  148. data = b""
  149. while len(data) < size:
  150. data += self.subp.stdout.read(size - len(data))
  151. return data
  152. def write(self, data):
  153. self.subp.stdin.write(data)
  154. return len(data)
  155. def inWaiting(self):
  156. # res = self.sel.select(0)
  157. res = self.poll.poll(0)
  158. if res:
  159. return 1
  160. return 0
  161. class ProcessPtyToTerminal:
  162. """Execute a process which creates a PTY and prints slave PTY as
  163. first line of its output, and emulate serial connection using
  164. this PTY."""
  165. def __init__(self, cmd):
  166. import subprocess
  167. import re
  168. import serial
  169. self.subp = subprocess.Popen(
  170. cmd.split(),
  171. bufsize=0,
  172. shell=False,
  173. preexec_fn=os.setsid,
  174. stdin=subprocess.PIPE,
  175. stdout=subprocess.PIPE,
  176. stderr=subprocess.PIPE,
  177. )
  178. pty_line = self.subp.stderr.readline().decode("utf-8")
  179. m = re.search(r"/dev/pts/[0-9]+", pty_line)
  180. if not m:
  181. print("Error: unable to find PTY device in startup line:", pty_line)
  182. self.close()
  183. sys.exit(1)
  184. pty = m.group()
  185. # rtscts, dsrdtr params are to workaround pyserial bug:
  186. # http://stackoverflow.com/questions/34831131/pyserial-does-not-play-well-with-virtual-port
  187. self.ser = serial.Serial(pty, interCharTimeout=1, rtscts=True, dsrdtr=True)
  188. def close(self):
  189. import signal
  190. os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
  191. def read(self, size=1):
  192. return self.ser.read(size)
  193. def write(self, data):
  194. return self.ser.write(data)
  195. def inWaiting(self):
  196. return self.ser.inWaiting()
  197. class Pyboard:
  198. def __init__(
  199. self, device, baudrate=115200, user="micro", password="python", wait=0, exclusive=True
  200. ):
  201. self.in_raw_repl = False
  202. self.use_raw_paste = True
  203. if device.startswith("exec:"):
  204. self.serial = ProcessToSerial(device[len("exec:") :])
  205. elif device.startswith("execpty:"):
  206. self.serial = ProcessPtyToTerminal(device[len("qemupty:") :])
  207. elif device and device[0].isdigit() and device[-1].isdigit() and device.count(".") == 3:
  208. # device looks like an IP address
  209. self.serial = TelnetToSerial(device, user, password, read_timeout=10)
  210. else:
  211. import serial
  212. # Set options, and exclusive if pyserial supports it
  213. serial_kwargs = {"baudrate": baudrate, "interCharTimeout": 1}
  214. if serial.__version__ >= "3.3":
  215. serial_kwargs["exclusive"] = exclusive
  216. delayed = False
  217. for attempt in range(wait + 1):
  218. try:
  219. self.serial = serial.Serial(device, **serial_kwargs)
  220. break
  221. except (OSError, IOError): # Py2 and Py3 have different errors
  222. if wait == 0:
  223. continue
  224. if attempt == 0:
  225. sys.stdout.write("Waiting {} seconds for pyboard ".format(wait))
  226. delayed = True
  227. time.sleep(1)
  228. sys.stdout.write(".")
  229. sys.stdout.flush()
  230. else:
  231. if delayed:
  232. print("")
  233. raise PyboardError("failed to access " + device)
  234. if delayed:
  235. print("")
  236. def close(self):
  237. self.serial.close()
  238. def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
  239. # if data_consumer is used then data is not accumulated and the ending must be 1 byte long
  240. assert data_consumer is None or len(ending) == 1
  241. data = self.serial.read(min_num_bytes)
  242. if data_consumer:
  243. data_consumer(data)
  244. timeout_count = 0
  245. while True:
  246. if data.endswith(ending):
  247. break
  248. elif self.serial.inWaiting() > 0:
  249. new_data = self.serial.read(1)
  250. if data_consumer:
  251. data_consumer(new_data)
  252. data = new_data
  253. else:
  254. data = data + new_data
  255. timeout_count = 0
  256. else:
  257. timeout_count += 1
  258. if timeout is not None and timeout_count >= 100 * timeout:
  259. break
  260. time.sleep(0.01)
  261. return data
  262. def enter_raw_repl(self, soft_reset=True):
  263. self.serial.write(b"\r\x03\x03") # ctrl-C twice: interrupt any running program
  264. # flush input (without relying on serial.flushInput())
  265. n = self.serial.inWaiting()
  266. while n > 0:
  267. self.serial.read(n)
  268. n = self.serial.inWaiting()
  269. self.serial.write(b"\r\x01") # ctrl-A: enter raw REPL
  270. if soft_reset:
  271. data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>")
  272. if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"):
  273. print(data)
  274. raise PyboardError("could not enter raw repl")
  275. self.serial.write(b"\x04") # ctrl-D: soft reset
  276. # Waiting for "soft reboot" independently to "raw REPL" (done below)
  277. # allows boot.py to print, which will show up after "soft reboot"
  278. # and before "raw REPL".
  279. data = self.read_until(1, b"soft reboot\r\n")
  280. if not data.endswith(b"soft reboot\r\n"):
  281. print(data)
  282. raise PyboardError("could not enter raw repl")
  283. data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n")
  284. if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"):
  285. print(data)
  286. raise PyboardError("could not enter raw repl")
  287. self.in_raw_repl = True
  288. def exit_raw_repl(self):
  289. self.serial.write(b"\r\x02") # ctrl-B: enter friendly REPL
  290. self.in_raw_repl = False
  291. def follow(self, timeout, data_consumer=None):
  292. # wait for normal output
  293. data = self.read_until(1, b"\x04", timeout=timeout, data_consumer=data_consumer)
  294. if not data.endswith(b"\x04"):
  295. raise PyboardError("timeout waiting for first EOF reception")
  296. data = data[:-1]
  297. # wait for error output
  298. data_err = self.read_until(1, b"\x04", timeout=timeout)
  299. if not data_err.endswith(b"\x04"):
  300. raise PyboardError("timeout waiting for second EOF reception")
  301. data_err = data_err[:-1]
  302. # return normal and error output
  303. return data, data_err
  304. def raw_paste_write(self, command_bytes):
  305. # Read initial header, with window size.
  306. data = self.serial.read(2)
  307. window_size = data[0] | data[1] << 8
  308. window_remain = window_size
  309. # Write out the command_bytes data.
  310. i = 0
  311. while i < len(command_bytes):
  312. while window_remain == 0 or self.serial.inWaiting():
  313. data = self.serial.read(1)
  314. if data == b"\x01":
  315. # Device indicated that a new window of data can be sent.
  316. window_remain += window_size
  317. elif data == b"\x04":
  318. # Device indicated abrupt end. Acknowledge it and finish.
  319. self.serial.write(b"\x04")
  320. return
  321. else:
  322. # Unexpected data from device.
  323. raise PyboardError("unexpected read during raw paste: {}".format(data))
  324. # Send out as much data as possible that fits within the allowed window.
  325. b = command_bytes[i : min(i + window_remain, len(command_bytes))]
  326. self.serial.write(b)
  327. window_remain -= len(b)
  328. i += len(b)
  329. # Indicate end of data.
  330. self.serial.write(b"\x04")
  331. # Wait for device to acknowledge end of data.
  332. data = self.read_until(1, b"\x04")
  333. if not data.endswith(b"\x04"):
  334. raise PyboardError("could not complete raw paste: {}".format(data))
  335. def exec_raw_no_follow(self, command):
  336. if isinstance(command, bytes):
  337. command_bytes = command
  338. else:
  339. command_bytes = bytes(command, encoding="utf8")
  340. # check we have a prompt
  341. data = self.read_until(1, b">")
  342. if not data.endswith(b">"):
  343. raise PyboardError("could not enter raw repl")
  344. if self.use_raw_paste:
  345. # Try to enter raw-paste mode.
  346. self.serial.write(b"\x05A\x01")
  347. data = self.serial.read(2)
  348. if data == b"R\x00":
  349. # Device understood raw-paste command but doesn't support it.
  350. pass
  351. elif data == b"R\x01":
  352. # Device supports raw-paste mode, write out the command using this mode.
  353. return self.raw_paste_write(command_bytes)
  354. else:
  355. # Device doesn't support raw-paste, fall back to normal raw REPL.
  356. data = self.read_until(1, b"w REPL; CTRL-B to exit\r\n>")
  357. if not data.endswith(b"w REPL; CTRL-B to exit\r\n>"):
  358. print(data)
  359. raise PyboardError("could not enter raw repl")
  360. # Don't try to use raw-paste mode again for this connection.
  361. self.use_raw_paste = False
  362. # Write command using standard raw REPL, 256 bytes every 10ms.
  363. for i in range(0, len(command_bytes), 256):
  364. self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))])
  365. time.sleep(0.01)
  366. self.serial.write(b"\x04")
  367. # check if we could exec command
  368. data = self.serial.read(2)
  369. if data != b"OK":
  370. raise PyboardError("could not exec command (response: %r)" % data)
  371. def exec_raw(self, command, timeout=10, data_consumer=None):
  372. self.exec_raw_no_follow(command)
  373. return self.follow(timeout, data_consumer)
  374. def eval(self, expression):
  375. ret = self.exec_("print({})".format(expression))
  376. ret = ret.strip()
  377. return ret
  378. def exec_(self, command, data_consumer=None):
  379. ret, ret_err = self.exec_raw(command, data_consumer=data_consumer)
  380. if ret_err:
  381. raise PyboardError("exception", ret, ret_err)
  382. return ret
  383. def execfile(self, filename):
  384. with open(filename, "rb") as f:
  385. pyfile = f.read()
  386. return self.exec_(pyfile)
  387. def get_time(self):
  388. t = str(self.eval("pyb.RTC().datetime()"), encoding="utf8")[1:-1].split(", ")
  389. return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6])
  390. def fs_ls(self, src):
  391. cmd = (
  392. "import uos\nfor f in uos.ilistdir(%s):\n"
  393. " print('{:12} {}{}'.format(f[3]if len(f)>3 else 0,f[0],'/'if f[1]&0x4000 else ''))"
  394. % (("'%s'" % src) if src else "")
  395. )
  396. self.exec_(cmd, data_consumer=stdout_write_bytes)
  397. def fs_cat(self, src, chunk_size=256):
  398. cmd = (
  399. "with open('%s') as f:\n while 1:\n"
  400. " b=f.read(%u)\n if not b:break\n print(b,end='')" % (src, chunk_size)
  401. )
  402. self.exec_(cmd, data_consumer=stdout_write_bytes)
  403. def fs_get(self, src, dest, chunk_size=256, progress_callback=None):
  404. if progress_callback:
  405. src_size = int(self.exec_("import os\nprint(os.stat('%s')[6])" % src))
  406. written = 0
  407. self.exec_("f=open('%s','rb')\nr=f.read" % src)
  408. with open(dest, "wb") as f:
  409. while True:
  410. data = bytearray()
  411. self.exec_("print(r(%u))" % chunk_size, data_consumer=lambda d: data.extend(d))
  412. assert data.endswith(b"\r\n\x04")
  413. try:
  414. data = ast.literal_eval(str(data[:-3], "ascii"))
  415. if not isinstance(data, bytes):
  416. raise ValueError("Not bytes")
  417. except (UnicodeError, ValueError) as e:
  418. raise PyboardError("fs_get: Could not interpret received data: %s" % str(e))
  419. if not data:
  420. break
  421. f.write(data)
  422. if progress_callback:
  423. written += len(data)
  424. progress_callback(written, src_size)
  425. self.exec_("f.close()")
  426. def fs_put(self, src, dest, chunk_size=256, progress_callback=None):
  427. if progress_callback:
  428. src_size = os.path.getsize(src)
  429. written = 0
  430. self.exec_("f=open('%s','wb')\nw=f.write" % dest)
  431. with open(src, "rb") as f:
  432. while True:
  433. data = f.read(chunk_size)
  434. if not data:
  435. break
  436. if sys.version_info < (3,):
  437. self.exec_("w(b" + repr(data) + ")")
  438. else:
  439. self.exec_("w(" + repr(data) + ")")
  440. if progress_callback:
  441. written += len(data)
  442. progress_callback(written, src_size)
  443. self.exec_("f.close()")
  444. def fs_mkdir(self, dir):
  445. self.exec_("import uos\nuos.mkdir('%s')" % dir)
  446. def fs_rmdir(self, dir):
  447. self.exec_("import uos\nuos.rmdir('%s')" % dir)
  448. def fs_rm(self, src):
  449. self.exec_("import uos\nuos.remove('%s')" % src)
  450. # in Python2 exec is a keyword so one must use "exec_"
  451. # but for Python3 we want to provide the nicer version "exec"
  452. setattr(Pyboard, "exec", Pyboard.exec_)
  453. def execfile(filename, device="/dev/ttyACM0", baudrate=115200, user="micro", password="python"):
  454. pyb = Pyboard(device, baudrate, user, password)
  455. pyb.enter_raw_repl()
  456. output = pyb.execfile(filename)
  457. stdout_write_bytes(output)
  458. pyb.exit_raw_repl()
  459. pyb.close()
  460. def filesystem_command(pyb, args, progress_callback=None):
  461. def fname_remote(src):
  462. if src.startswith(":"):
  463. src = src[1:]
  464. return src
  465. def fname_cp_dest(src, dest):
  466. src = src.rsplit("/", 1)[-1]
  467. if dest is None or dest == "":
  468. dest = src
  469. elif dest == ".":
  470. dest = "./" + src
  471. elif dest.endswith("/"):
  472. dest += src
  473. return dest
  474. cmd = args[0]
  475. args = args[1:]
  476. try:
  477. if cmd == "cp":
  478. srcs = args[:-1]
  479. dest = args[-1]
  480. if srcs[0].startswith("./") or dest.startswith(":"):
  481. op = pyb.fs_put
  482. fmt = "cp %s :%s"
  483. dest = fname_remote(dest)
  484. else:
  485. op = pyb.fs_get
  486. fmt = "cp :%s %s"
  487. for src in srcs:
  488. src = fname_remote(src)
  489. dest2 = fname_cp_dest(src, dest)
  490. print(fmt % (src, dest2))
  491. op(src, dest2, progress_callback=progress_callback)
  492. else:
  493. op = {
  494. "ls": pyb.fs_ls,
  495. "cat": pyb.fs_cat,
  496. "mkdir": pyb.fs_mkdir,
  497. "rmdir": pyb.fs_rmdir,
  498. "rm": pyb.fs_rm,
  499. }[cmd]
  500. if cmd == "ls" and not args:
  501. args = [""]
  502. for src in args:
  503. src = fname_remote(src)
  504. print("%s :%s" % (cmd, src))
  505. op(src)
  506. except PyboardError as er:
  507. print(str(er.args[2], "ascii"))
  508. pyb.exit_raw_repl()
  509. pyb.close()
  510. sys.exit(1)
  511. _injected_import_hook_code = """\
  512. import uos, uio
  513. class _FS:
  514. class File(uio.IOBase):
  515. def __init__(self):
  516. self.off = 0
  517. def ioctl(self, request, arg):
  518. return 0
  519. def readinto(self, buf):
  520. buf[:] = memoryview(_injected_buf)[self.off:self.off + len(buf)]
  521. self.off += len(buf)
  522. return len(buf)
  523. mount = umount = chdir = lambda *args: None
  524. def stat(self, path):
  525. if path == '_injected.mpy':
  526. return tuple(0 for _ in range(10))
  527. else:
  528. raise OSError(-2) # ENOENT
  529. def open(self, path, mode):
  530. return self.File()
  531. uos.mount(_FS(), '/_')
  532. uos.chdir('/_')
  533. from _injected import *
  534. uos.umount('/_')
  535. del _injected_buf, _FS
  536. """
  537. def main():
  538. import argparse
  539. cmd_parser = argparse.ArgumentParser(description="Run scripts on the pyboard.")
  540. cmd_parser.add_argument(
  541. "-d",
  542. "--device",
  543. default=os.environ.get("PYBOARD_DEVICE", "/dev/ttyACM0"),
  544. help="the serial device or the IP address of the pyboard",
  545. )
  546. cmd_parser.add_argument(
  547. "-b",
  548. "--baudrate",
  549. default=os.environ.get("PYBOARD_BAUDRATE", "115200"),
  550. help="the baud rate of the serial device",
  551. )
  552. cmd_parser.add_argument("-u", "--user", default="micro", help="the telnet login username")
  553. cmd_parser.add_argument("-p", "--password", default="python", help="the telnet login password")
  554. cmd_parser.add_argument("-c", "--command", help="program passed in as string")
  555. cmd_parser.add_argument(
  556. "-w",
  557. "--wait",
  558. default=0,
  559. type=int,
  560. help="seconds to wait for USB connected board to become available",
  561. )
  562. group = cmd_parser.add_mutually_exclusive_group()
  563. group.add_argument(
  564. "--soft-reset",
  565. default=True,
  566. action="store_true",
  567. help="Whether to perform a soft reset when connecting to the board [default]",
  568. )
  569. group.add_argument(
  570. "--no-soft-reset",
  571. action="store_false",
  572. dest="soft_reset",
  573. )
  574. group = cmd_parser.add_mutually_exclusive_group()
  575. group.add_argument(
  576. "--follow",
  577. action="store_true",
  578. default=None,
  579. help="follow the output after running the scripts [default if no scripts given]",
  580. )
  581. group.add_argument(
  582. "--no-follow",
  583. action="store_false",
  584. dest="follow",
  585. )
  586. group = cmd_parser.add_mutually_exclusive_group()
  587. group.add_argument(
  588. "--exclusive",
  589. action="store_true",
  590. default=True,
  591. help="Open the serial device for exclusive access [default]",
  592. )
  593. group.add_argument(
  594. "--no-exclusive",
  595. action="store_false",
  596. dest="exclusive",
  597. )
  598. cmd_parser.add_argument(
  599. "-f",
  600. "--filesystem",
  601. action="store_true",
  602. help="perform a filesystem action: "
  603. "cp local :device | cp :device local | cat path | ls [path] | rm path | mkdir path | rmdir path",
  604. )
  605. cmd_parser.add_argument("files", nargs="*", help="input files")
  606. args = cmd_parser.parse_args()
  607. # open the connection to the pyboard
  608. try:
  609. pyb = Pyboard(
  610. args.device, args.baudrate, args.user, args.password, args.wait, args.exclusive
  611. )
  612. except PyboardError as er:
  613. print(er)
  614. sys.exit(1)
  615. # run any command or file(s)
  616. if args.command is not None or args.filesystem or len(args.files):
  617. # we must enter raw-REPL mode to execute commands
  618. # this will do a soft-reset of the board
  619. try:
  620. pyb.enter_raw_repl(args.soft_reset)
  621. except PyboardError as er:
  622. print(er)
  623. pyb.close()
  624. sys.exit(1)
  625. def execbuffer(buf):
  626. try:
  627. if args.follow is None or args.follow:
  628. ret, ret_err = pyb.exec_raw(
  629. buf, timeout=None, data_consumer=stdout_write_bytes
  630. )
  631. else:
  632. pyb.exec_raw_no_follow(buf)
  633. ret_err = None
  634. except PyboardError as er:
  635. print(er)
  636. pyb.close()
  637. sys.exit(1)
  638. except KeyboardInterrupt:
  639. sys.exit(1)
  640. if ret_err:
  641. pyb.exit_raw_repl()
  642. pyb.close()
  643. stdout_write_bytes(ret_err)
  644. sys.exit(1)
  645. # do filesystem commands, if given
  646. if args.filesystem:
  647. filesystem_command(pyb, args.files)
  648. del args.files[:]
  649. # run the command, if given
  650. if args.command is not None:
  651. execbuffer(args.command.encode("utf-8"))
  652. # run any files
  653. for filename in args.files:
  654. with open(filename, "rb") as f:
  655. pyfile = f.read()
  656. if filename.endswith(".mpy") and pyfile[0] == ord("M"):
  657. pyb.exec_("_injected_buf=" + repr(pyfile))
  658. pyfile = _injected_import_hook_code
  659. execbuffer(pyfile)
  660. # exiting raw-REPL just drops to friendly-REPL mode
  661. pyb.exit_raw_repl()
  662. # if asked explicitly, or no files given, then follow the output
  663. if args.follow or (args.command is None and not args.filesystem and len(args.files) == 0):
  664. try:
  665. ret, ret_err = pyb.follow(timeout=None, data_consumer=stdout_write_bytes)
  666. except PyboardError as er:
  667. print(er)
  668. sys.exit(1)
  669. except KeyboardInterrupt:
  670. sys.exit(1)
  671. if ret_err:
  672. pyb.close()
  673. stdout_write_bytes(ret_err)
  674. sys.exit(1)
  675. # close the connection to the pyboard
  676. pyb.close()
  677. if __name__ == "__main__":
  678. main()