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