2014-10-26 11:39:22 -04:00
|
|
|
#!/usr/bin/env python
|
2020-06-03 18:40:05 -04:00
|
|
|
|
2021-04-23 15:26:42 -04:00
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2014-2019 Damien P. George
|
2020-06-03 18:40:05 -04:00
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2017 Paul Sokolovsky
|
|
|
|
# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)
|
2017-07-22 10:12:15 -04:00
|
|
|
#
|
2020-06-03 18:40:05 -04:00
|
|
|
# SPDX-License-Identifier: MIT
|
2014-10-26 11:39:22 -04:00
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
"""
|
|
|
|
pyboard interface
|
|
|
|
|
|
|
|
This module provides the Pyboard class, used to communicate with and
|
2017-10-07 17:04:57 -04:00
|
|
|
control a MicroPython device over a communication channel. Both real
|
|
|
|
boards and emulated devices (e.g. running in QEMU) are supported.
|
|
|
|
Various communication channels are supported, including a serial
|
|
|
|
connection, telnet-style network connection, external process
|
|
|
|
connection.
|
2014-03-24 08:42:06 -04:00
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
|
|
|
import pyboard
|
|
|
|
pyb = pyboard.Pyboard('/dev/ttyACM0')
|
2015-06-28 08:01:27 -04:00
|
|
|
|
|
|
|
Or:
|
|
|
|
|
|
|
|
pyb = pyboard.Pyboard('192.168.1.1')
|
|
|
|
|
|
|
|
Then:
|
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
pyb.enter_raw_repl()
|
2021-04-22 20:55:39 -04:00
|
|
|
pyb.exec('import pyb')
|
2014-05-03 13:14:34 -04:00
|
|
|
pyb.exec('pyb.LED(1).on()')
|
2014-03-24 08:42:06 -04:00
|
|
|
pyb.exit_raw_repl()
|
|
|
|
|
2015-08-08 08:03:08 -04:00
|
|
|
Note: if using Python2 then pyb.exec must be written as pyb.exec_.
|
2014-04-03 17:12:01 -04:00
|
|
|
To run a script from the local machine on the board and print out the results:
|
|
|
|
|
|
|
|
import pyboard
|
|
|
|
pyboard.execfile('test.py', device='/dev/ttyACM0')
|
|
|
|
|
2014-05-03 13:14:34 -04:00
|
|
|
This script can also be run directly. To execute a local script, use:
|
|
|
|
|
2014-10-26 11:39:22 -04:00
|
|
|
./pyboard.py test.py
|
|
|
|
|
|
|
|
Or:
|
|
|
|
|
2014-05-03 13:14:34 -04:00
|
|
|
python pyboard.py test.py
|
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
"""
|
|
|
|
|
2014-10-26 11:39:22 -04:00
|
|
|
import sys
|
2014-03-24 08:42:06 -04:00
|
|
|
import time
|
2017-04-04 10:46:02 -04:00
|
|
|
import os
|
2014-03-24 08:42:06 -04:00
|
|
|
|
2015-08-08 08:03:08 -04:00
|
|
|
try:
|
|
|
|
stdout = sys.stdout.buffer
|
|
|
|
except AttributeError:
|
|
|
|
# Python2 doesn't have buffer attr
|
|
|
|
stdout = sys.stdout
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-04-19 16:31:28 -04:00
|
|
|
def stdout_write_bytes(b):
|
2015-11-07 04:11:12 -05:00
|
|
|
b = b.replace(b"\x04", b"")
|
2015-08-08 08:03:08 -04:00
|
|
|
stdout.write(b)
|
|
|
|
stdout.flush()
|
2015-04-19 16:31:28 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2014-04-13 08:48:33 -04:00
|
|
|
class PyboardError(BaseException):
|
|
|
|
pass
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-06-28 08:01:27 -04:00
|
|
|
class TelnetToSerial:
|
|
|
|
def __init__(self, ip, user, password, read_timeout=None):
|
2018-10-16 06:36:24 -04:00
|
|
|
self.tn = None
|
2015-06-28 08:01:27 -04:00
|
|
|
import telnetlib
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-06-28 08:01:27 -04:00
|
|
|
self.tn = telnetlib.Telnet(ip, timeout=15)
|
|
|
|
self.read_timeout = read_timeout
|
2021-03-15 09:57:36 -04:00
|
|
|
if b"Login as:" in self.tn.read_until(b"Login as:", timeout=read_timeout):
|
|
|
|
self.tn.write(bytes(user, "ascii") + b"\r\n")
|
2015-06-28 08:01:27 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
if b"Password:" in self.tn.read_until(b"Password:", timeout=read_timeout):
|
2015-06-28 08:01:27 -04:00
|
|
|
# needed because of internal implementation details of the telnet server
|
|
|
|
time.sleep(0.2)
|
2021-03-15 09:57:36 -04:00
|
|
|
self.tn.write(bytes(password, "ascii") + b"\r\n")
|
2015-06-28 08:01:27 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
if b"for more information." in self.tn.read_until(
|
|
|
|
b'Type "help()" for more information.', timeout=read_timeout
|
|
|
|
):
|
2017-05-29 03:08:14 -04:00
|
|
|
# login successful
|
2015-06-28 08:01:27 -04:00
|
|
|
from collections import deque
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-06-28 08:01:27 -04:00
|
|
|
self.fifo = deque()
|
|
|
|
return
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
raise PyboardError("Failed to establish a telnet connection with the board")
|
2015-06-28 08:01:27 -04:00
|
|
|
|
|
|
|
def __del__(self):
|
|
|
|
self.close()
|
|
|
|
|
|
|
|
def close(self):
|
2018-10-16 06:36:24 -04:00
|
|
|
if self.tn:
|
2015-06-28 08:01:27 -04:00
|
|
|
self.tn.close()
|
|
|
|
|
|
|
|
def read(self, size=1):
|
|
|
|
while len(self.fifo) < size:
|
|
|
|
timeout_count = 0
|
|
|
|
data = self.tn.read_eager()
|
|
|
|
if len(data):
|
|
|
|
self.fifo.extend(data)
|
|
|
|
timeout_count = 0
|
|
|
|
else:
|
|
|
|
time.sleep(0.25)
|
|
|
|
if self.read_timeout is not None and timeout_count > 4 * self.read_timeout:
|
|
|
|
break
|
|
|
|
timeout_count += 1
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
data = b""
|
2015-06-28 08:01:27 -04:00
|
|
|
while len(data) < size and len(self.fifo) > 0:
|
|
|
|
data += bytes([self.fifo.popleft()])
|
|
|
|
return data
|
|
|
|
|
|
|
|
def write(self, data):
|
|
|
|
self.tn.write(data)
|
|
|
|
return len(data)
|
|
|
|
|
|
|
|
def inWaiting(self):
|
|
|
|
n_waiting = len(self.fifo)
|
|
|
|
if not n_waiting:
|
|
|
|
data = self.tn.read_eager()
|
|
|
|
self.fifo.extend(data)
|
|
|
|
return len(data)
|
|
|
|
else:
|
|
|
|
return n_waiting
|
|
|
|
|
2017-04-04 10:46:02 -04:00
|
|
|
|
|
|
|
class ProcessToSerial:
|
|
|
|
"Execute a process and emulate serial connection using its stdin/stdout."
|
|
|
|
|
|
|
|
def __init__(self, cmd):
|
|
|
|
import subprocess
|
2021-03-15 09:57:36 -04:00
|
|
|
|
|
|
|
self.subp = subprocess.Popen(
|
|
|
|
cmd.split(),
|
|
|
|
bufsize=0,
|
|
|
|
shell=True,
|
|
|
|
preexec_fn=os.setsid,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
)
|
2017-04-04 10:46:02 -04:00
|
|
|
|
|
|
|
# Initially was implemented with selectors, but that adds Python3
|
|
|
|
# dependency. However, there can be race conditions communicating
|
|
|
|
# with a particular child process (like QEMU), and selectors may
|
|
|
|
# still work better in that case, so left inplace for now.
|
|
|
|
#
|
2021-03-15 09:57:36 -04:00
|
|
|
# import selectors
|
|
|
|
# self.sel = selectors.DefaultSelector()
|
|
|
|
# self.sel.register(self.subp.stdout, selectors.EVENT_READ)
|
2017-04-04 10:46:02 -04:00
|
|
|
|
|
|
|
import select
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2017-04-04 10:46:02 -04:00
|
|
|
self.poll = select.poll()
|
|
|
|
self.poll.register(self.subp.stdout.fileno())
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
import signal
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2017-04-04 10:46:02 -04:00
|
|
|
os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
|
|
|
|
|
|
|
|
def read(self, size=1):
|
|
|
|
data = b""
|
|
|
|
while len(data) < size:
|
|
|
|
data += self.subp.stdout.read(size - len(data))
|
|
|
|
return data
|
|
|
|
|
|
|
|
def write(self, data):
|
|
|
|
self.subp.stdin.write(data)
|
|
|
|
return len(data)
|
|
|
|
|
|
|
|
def inWaiting(self):
|
2021-03-15 09:57:36 -04:00
|
|
|
# res = self.sel.select(0)
|
2017-04-04 10:46:02 -04:00
|
|
|
res = self.poll.poll(0)
|
|
|
|
if res:
|
|
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
class ProcessPtyToTerminal:
|
2020-06-14 15:28:32 -04:00
|
|
|
"""Execute a process which creates a PTY and prints secondary PTY as
|
2017-04-04 10:46:02 -04:00
|
|
|
first line of its output, and emulate serial connection using
|
|
|
|
this PTY."""
|
|
|
|
|
|
|
|
def __init__(self, cmd):
|
|
|
|
import subprocess
|
|
|
|
import re
|
|
|
|
import serial
|
2021-03-15 09:57:36 -04:00
|
|
|
|
|
|
|
self.subp = subprocess.Popen(
|
|
|
|
cmd.split(),
|
|
|
|
bufsize=0,
|
|
|
|
shell=False,
|
|
|
|
preexec_fn=os.setsid,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
)
|
2017-04-04 10:46:02 -04:00
|
|
|
pty_line = self.subp.stderr.readline().decode("utf-8")
|
|
|
|
m = re.search(r"/dev/pts/[0-9]+", pty_line)
|
|
|
|
if not m:
|
|
|
|
print("Error: unable to find PTY device in startup line:", pty_line)
|
|
|
|
self.close()
|
|
|
|
sys.exit(1)
|
|
|
|
pty = m.group()
|
2017-04-05 05:30:39 -04:00
|
|
|
# rtscts, dsrdtr params are to workaround pyserial bug:
|
|
|
|
# http://stackoverflow.com/questions/34831131/pyserial-does-not-play-well-with-virtual-port
|
|
|
|
self.ser = serial.Serial(pty, interCharTimeout=1, rtscts=True, dsrdtr=True)
|
2017-04-04 10:46:02 -04:00
|
|
|
|
|
|
|
def close(self):
|
|
|
|
import signal
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2017-04-04 10:46:02 -04:00
|
|
|
os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
|
|
|
|
|
|
|
|
def read(self, size=1):
|
|
|
|
return self.ser.read(size)
|
|
|
|
|
|
|
|
def write(self, data):
|
|
|
|
return self.ser.write(data)
|
|
|
|
|
|
|
|
def inWaiting(self):
|
|
|
|
return self.ser.inWaiting()
|
|
|
|
|
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
class Pyboard:
|
2021-03-15 09:57:36 -04:00
|
|
|
def __init__(self, device, baudrate=115200, user="micro", password="python", wait=0):
|
2017-04-04 10:46:02 -04:00
|
|
|
if device.startswith("exec:"):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial = ProcessToSerial(device[len("exec:") :])
|
2017-04-04 10:46:02 -04:00
|
|
|
elif device.startswith("execpty:"):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial = ProcessPtyToTerminal(device[len("qemupty:") :])
|
|
|
|
elif device and device[0].isdigit() and device[-1].isdigit() and device.count(".") == 3:
|
2015-06-28 08:01:27 -04:00
|
|
|
# device looks like an IP address
|
|
|
|
self.serial = TelnetToSerial(device, user, password, read_timeout=10)
|
|
|
|
else:
|
|
|
|
import serial
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-12-01 01:13:50 -05:00
|
|
|
delayed = False
|
|
|
|
for attempt in range(wait + 1):
|
|
|
|
try:
|
|
|
|
self.serial = serial.Serial(device, baudrate=baudrate, interCharTimeout=1)
|
|
|
|
break
|
2021-03-15 09:57:36 -04:00
|
|
|
except (OSError, IOError): # Py2 and Py3 have different errors
|
2015-12-01 01:13:50 -05:00
|
|
|
if wait == 0:
|
|
|
|
continue
|
|
|
|
if attempt == 0:
|
2021-03-15 09:57:36 -04:00
|
|
|
sys.stdout.write("Waiting {} seconds for pyboard ".format(wait))
|
2015-12-01 01:13:50 -05:00
|
|
|
delayed = True
|
|
|
|
time.sleep(1)
|
2021-03-15 09:57:36 -04:00
|
|
|
sys.stdout.write(".")
|
2015-12-01 01:13:50 -05:00
|
|
|
sys.stdout.flush()
|
|
|
|
else:
|
|
|
|
if delayed:
|
2021-03-15 09:57:36 -04:00
|
|
|
print("")
|
|
|
|
raise PyboardError("failed to access " + device)
|
2015-12-01 01:13:50 -05:00
|
|
|
if delayed:
|
2021-03-15 09:57:36 -04:00
|
|
|
print("")
|
2014-03-24 08:42:06 -04:00
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self.serial.close()
|
|
|
|
|
2014-10-26 11:39:22 -04:00
|
|
|
def read_until(self, min_num_bytes, ending, timeout=10, data_consumer=None):
|
2021-04-22 20:55:39 -04:00
|
|
|
# if data_consumer is used then data is not accumulated and the ending must be 1 byte long
|
|
|
|
assert data_consumer is None or len(ending) == 1
|
|
|
|
|
2014-04-13 20:47:36 -04:00
|
|
|
data = self.serial.read(min_num_bytes)
|
2014-10-26 11:39:22 -04:00
|
|
|
if data_consumer:
|
|
|
|
data_consumer(data)
|
2014-04-13 20:47:36 -04:00
|
|
|
timeout_count = 0
|
|
|
|
while True:
|
2014-10-26 11:39:22 -04:00
|
|
|
if data.endswith(ending):
|
2014-04-13 20:47:36 -04:00
|
|
|
break
|
2014-10-26 11:39:22 -04:00
|
|
|
elif self.serial.inWaiting() > 0:
|
|
|
|
new_data = self.serial.read(1)
|
|
|
|
if data_consumer:
|
|
|
|
data_consumer(new_data)
|
2021-04-22 20:55:39 -04:00
|
|
|
data = new_data
|
|
|
|
else:
|
|
|
|
data = data + new_data
|
2014-10-26 11:39:22 -04:00
|
|
|
timeout_count = 0
|
2014-04-13 20:47:36 -04:00
|
|
|
else:
|
|
|
|
timeout_count += 1
|
2015-07-28 19:21:22 -04:00
|
|
|
if timeout is not None and timeout_count >= 100 * timeout:
|
2014-04-13 20:47:36 -04:00
|
|
|
break
|
2015-07-25 17:49:25 -04:00
|
|
|
time.sleep(0.01)
|
2014-04-13 20:47:36 -04:00
|
|
|
return data
|
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
def enter_raw_repl(self):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(b"\r\x03\x03") # ctrl-C twice: interrupt any running program
|
2015-06-15 18:28:04 -04:00
|
|
|
|
|
|
|
# flush input (without relying on serial.flushInput())
|
|
|
|
n = self.serial.inWaiting()
|
|
|
|
while n > 0:
|
|
|
|
self.serial.read(n)
|
|
|
|
n = self.serial.inWaiting()
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(b"\r\x01") # ctrl-A: enter raw REPL
|
|
|
|
data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n>")
|
|
|
|
if not data.endswith(b"raw REPL; CTRL-B to exit\r\n>"):
|
2014-10-26 11:39:22 -04:00
|
|
|
print(data)
|
2021-03-15 09:57:36 -04:00
|
|
|
raise PyboardError("could not enter raw repl")
|
2015-06-15 18:28:04 -04:00
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(b"\x04") # ctrl-D: soft reset
|
|
|
|
data = self.read_until(1, b"soft reboot\r\n")
|
|
|
|
if not data.endswith(b"soft reboot\r\n"):
|
2015-12-03 19:05:53 -05:00
|
|
|
print(data)
|
2021-03-15 09:57:36 -04:00
|
|
|
raise PyboardError("could not enter raw repl")
|
2015-12-03 19:05:53 -05:00
|
|
|
# By splitting this into 2 reads, it allows boot.py to print stuff,
|
|
|
|
# which will show up after the soft reboot and before the raw REPL.
|
2021-03-15 09:57:36 -04:00
|
|
|
data = self.read_until(1, b"raw REPL; CTRL-B to exit\r\n")
|
|
|
|
if not data.endswith(b"raw REPL; CTRL-B to exit\r\n"):
|
2014-03-24 08:42:06 -04:00
|
|
|
print(data)
|
2021-03-15 09:57:36 -04:00
|
|
|
raise PyboardError("could not enter raw repl")
|
2014-03-24 08:42:06 -04:00
|
|
|
|
|
|
|
def exit_raw_repl(self):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(b"\r\x02") # ctrl-B: enter friendly REPL
|
2014-03-24 08:42:06 -04:00
|
|
|
|
2014-11-30 16:30:53 -05:00
|
|
|
def follow(self, timeout, data_consumer=None):
|
2014-10-26 11:39:22 -04:00
|
|
|
# wait for normal output
|
2021-03-15 09:57:36 -04:00
|
|
|
data = self.read_until(1, b"\x04", timeout=timeout, data_consumer=data_consumer)
|
|
|
|
if not data.endswith(b"\x04"):
|
|
|
|
raise PyboardError("timeout waiting for first EOF reception")
|
2014-10-26 11:39:22 -04:00
|
|
|
data = data[:-1]
|
2014-03-24 08:42:06 -04:00
|
|
|
|
2014-10-26 11:39:22 -04:00
|
|
|
# wait for error output
|
2021-03-15 09:57:36 -04:00
|
|
|
data_err = self.read_until(1, b"\x04", timeout=timeout)
|
|
|
|
if not data_err.endswith(b"\x04"):
|
|
|
|
raise PyboardError("timeout waiting for second EOF reception")
|
2015-07-25 17:44:56 -04:00
|
|
|
data_err = data_err[:-1]
|
2014-10-26 11:39:22 -04:00
|
|
|
|
|
|
|
# return normal and error output
|
|
|
|
return data, data_err
|
|
|
|
|
2015-05-17 01:48:54 -04:00
|
|
|
def exec_raw_no_follow(self, command):
|
2014-10-19 09:54:52 -04:00
|
|
|
if isinstance(command, bytes):
|
|
|
|
command_bytes = command
|
|
|
|
else:
|
2021-03-15 09:57:36 -04:00
|
|
|
command_bytes = bytes(command, encoding="utf8")
|
2014-10-26 11:39:22 -04:00
|
|
|
|
2015-06-15 18:28:04 -04:00
|
|
|
# check we have a prompt
|
2021-03-15 09:57:36 -04:00
|
|
|
data = self.read_until(1, b">")
|
|
|
|
if not data.endswith(b">"):
|
|
|
|
raise PyboardError("could not enter raw repl")
|
2015-06-15 18:28:04 -04:00
|
|
|
|
2014-10-26 11:39:22 -04:00
|
|
|
# write command
|
2014-12-20 13:09:04 -05:00
|
|
|
for i in range(0, len(command_bytes), 256):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(command_bytes[i : min(i + 256, len(command_bytes))])
|
2014-03-24 08:42:06 -04:00
|
|
|
time.sleep(0.01)
|
2021-03-15 09:57:36 -04:00
|
|
|
self.serial.write(b"\x04")
|
2014-10-26 11:39:22 -04:00
|
|
|
|
|
|
|
# check if we could exec command
|
2014-03-24 08:42:06 -04:00
|
|
|
data = self.serial.read(2)
|
2021-03-15 09:57:36 -04:00
|
|
|
if data != b"OK":
|
|
|
|
raise PyboardError("could not exec command (response: %r)" % data)
|
2014-10-26 11:39:22 -04:00
|
|
|
|
2015-05-17 01:48:54 -04:00
|
|
|
def exec_raw(self, command, timeout=10, data_consumer=None):
|
2021-03-15 09:57:36 -04:00
|
|
|
self.exec_raw_no_follow(command)
|
2014-11-30 16:30:53 -05:00
|
|
|
return self.follow(timeout, data_consumer)
|
2014-10-26 11:39:22 -04:00
|
|
|
|
|
|
|
def eval(self, expression):
|
2021-03-15 09:57:36 -04:00
|
|
|
ret = self.exec_("print({})".format(expression))
|
2014-10-26 11:39:22 -04:00
|
|
|
ret = ret.strip()
|
|
|
|
return ret
|
|
|
|
|
2021-04-23 15:26:42 -04:00
|
|
|
def exec_(self, command, data_consumer=None):
|
|
|
|
ret, ret_err = self.exec_raw(command, data_consumer=data_consumer)
|
2014-10-26 11:39:22 -04:00
|
|
|
if ret_err:
|
2021-03-15 09:57:36 -04:00
|
|
|
raise PyboardError("exception", ret, ret_err)
|
2014-10-26 11:39:22 -04:00
|
|
|
return ret
|
2014-04-03 17:12:01 -04:00
|
|
|
|
|
|
|
def execfile(self, filename):
|
2021-03-15 09:57:36 -04:00
|
|
|
with open(filename, "rb") as f:
|
2014-04-03 17:12:01 -04:00
|
|
|
pyfile = f.read()
|
2015-08-08 08:03:08 -04:00
|
|
|
return self.exec_(pyfile)
|
2014-03-24 08:42:06 -04:00
|
|
|
|
|
|
|
def get_time(self):
|
2021-03-15 09:57:36 -04:00
|
|
|
t = str(self.eval("pyb.RTC().datetime()"), encoding="utf8")[1:-1].split(", ")
|
2014-04-21 15:18:37 -04:00
|
|
|
return int(t[4]) * 3600 + int(t[5]) * 60 + int(t[6])
|
2014-03-24 08:42:06 -04:00
|
|
|
|
2021-04-23 15:26:42 -04:00
|
|
|
def fs_ls(self, src):
|
|
|
|
cmd = (
|
|
|
|
"import uos\nfor f in uos.ilistdir(%s):\n"
|
|
|
|
" print('{:12} {}{}'.format(f[3]if len(f)>3 else 0,f[0],'/'if f[1]&0x4000 else ''))"
|
|
|
|
% (("'%s'" % src) if src else "")
|
|
|
|
)
|
|
|
|
self.exec_(cmd, data_consumer=stdout_write_bytes)
|
|
|
|
|
|
|
|
def fs_cat(self, src, chunk_size=256):
|
|
|
|
cmd = (
|
|
|
|
"with open('%s') as f:\n while 1:\n"
|
|
|
|
" b=f.read(%u)\n if not b:break\n print(b,end='')" % (src, chunk_size)
|
|
|
|
)
|
|
|
|
self.exec_(cmd, data_consumer=stdout_write_bytes)
|
|
|
|
|
|
|
|
def fs_get(self, src, dest, chunk_size=256):
|
|
|
|
self.exec_("f=open('%s','rb')\nr=f.read" % src)
|
|
|
|
with open(dest, "wb") as f:
|
|
|
|
while True:
|
|
|
|
data = bytearray()
|
|
|
|
self.exec_("print(r(%u))" % chunk_size, data_consumer=lambda d: data.extend(d))
|
|
|
|
assert data.endswith(b"\r\n\x04")
|
|
|
|
data = eval(str(data[:-3], "ascii"))
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
f.write(data)
|
|
|
|
self.exec_("f.close()")
|
|
|
|
|
|
|
|
def fs_put(self, src, dest, chunk_size=256):
|
|
|
|
self.exec_("f=open('%s','wb')\nw=f.write" % dest)
|
|
|
|
with open(src, "rb") as f:
|
|
|
|
while True:
|
|
|
|
data = f.read(chunk_size)
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
if sys.version_info < (3,):
|
|
|
|
self.exec_("w(b" + repr(data) + ")")
|
|
|
|
else:
|
|
|
|
self.exec_("w(" + repr(data) + ")")
|
|
|
|
self.exec_("f.close()")
|
|
|
|
|
|
|
|
def fs_mkdir(self, dir):
|
|
|
|
self.exec_("import uos\nuos.mkdir('%s')" % dir)
|
|
|
|
|
|
|
|
def fs_rmdir(self, dir):
|
|
|
|
self.exec_("import uos\nuos.rmdir('%s')" % dir)
|
|
|
|
|
|
|
|
def fs_rm(self, src):
|
|
|
|
self.exec_("import uos\nuos.remove('%s')" % src)
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2015-08-08 08:03:08 -04:00
|
|
|
# in Python2 exec is a keyword so one must use "exec_"
|
|
|
|
# but for Python3 we want to provide the nicer version "exec"
|
|
|
|
setattr(Pyboard, "exec", Pyboard.exec_)
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
|
|
|
def execfile(filename, device="/dev/ttyACM0", baudrate=115200, user="micro", password="python"):
|
2015-06-28 08:01:27 -04:00
|
|
|
pyb = Pyboard(device, baudrate, user, password)
|
2014-04-03 17:12:01 -04:00
|
|
|
pyb.enter_raw_repl()
|
|
|
|
output = pyb.execfile(filename)
|
2015-04-19 16:31:28 -04:00
|
|
|
stdout_write_bytes(output)
|
2014-04-03 17:12:01 -04:00
|
|
|
pyb.exit_raw_repl()
|
|
|
|
pyb.close()
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2021-04-23 15:26:42 -04:00
|
|
|
def filesystem_command(pyb, args):
|
|
|
|
def fname_remote(src):
|
|
|
|
if src.startswith(":"):
|
|
|
|
src = src[1:]
|
|
|
|
return src
|
|
|
|
|
|
|
|
def fname_cp_dest(src, dest):
|
|
|
|
src = src.rsplit("/", 1)[-1]
|
|
|
|
if dest is None or dest == "":
|
|
|
|
dest = src
|
|
|
|
elif dest == ".":
|
|
|
|
dest = "./" + src
|
|
|
|
elif dest.endswith("/"):
|
|
|
|
dest += src
|
|
|
|
return dest
|
|
|
|
|
|
|
|
cmd = args[0]
|
|
|
|
args = args[1:]
|
|
|
|
try:
|
|
|
|
if cmd == "cp":
|
|
|
|
srcs = args[:-1]
|
|
|
|
dest = args[-1]
|
|
|
|
if srcs[0].startswith("./") or dest.startswith(":"):
|
|
|
|
op = pyb.fs_put
|
|
|
|
fmt = "cp %s :%s"
|
|
|
|
dest = fname_remote(dest)
|
|
|
|
else:
|
|
|
|
op = pyb.fs_get
|
|
|
|
fmt = "cp :%s %s"
|
|
|
|
for src in srcs:
|
|
|
|
src = fname_remote(src)
|
|
|
|
dest2 = fname_cp_dest(src, dest)
|
|
|
|
print(fmt % (src, dest2))
|
|
|
|
op(src, dest2)
|
|
|
|
else:
|
|
|
|
op = {
|
|
|
|
"ls": pyb.fs_ls,
|
|
|
|
"cat": pyb.fs_cat,
|
|
|
|
"mkdir": pyb.fs_mkdir,
|
|
|
|
"rmdir": pyb.fs_rmdir,
|
|
|
|
"rm": pyb.fs_rm,
|
|
|
|
}[cmd]
|
|
|
|
if cmd == "ls" and not args:
|
|
|
|
args = [""]
|
|
|
|
for src in args:
|
|
|
|
src = fname_remote(src)
|
|
|
|
print("%s :%s" % (cmd, src))
|
|
|
|
op(src)
|
|
|
|
except PyboardError as er:
|
|
|
|
print(str(er.args[2], "ascii"))
|
|
|
|
pyb.exit_raw_repl()
|
|
|
|
pyb.close()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
_injected_import_hook_code = """\
|
|
|
|
import uos, uio
|
|
|
|
class _FS:
|
|
|
|
class File(uio.IOBase):
|
|
|
|
def __init__(self):
|
|
|
|
self.off = 0
|
|
|
|
def ioctl(self, request, arg):
|
|
|
|
return 0
|
|
|
|
def readinto(self, buf):
|
|
|
|
buf[:] = memoryview(_injected_buf)[self.off:self.off + len(buf)]
|
|
|
|
self.off += len(buf)
|
|
|
|
return len(buf)
|
|
|
|
mount = umount = chdir = lambda *args: None
|
|
|
|
def stat(self, path):
|
|
|
|
if path == '_injected.mpy':
|
|
|
|
return tuple(0 for _ in range(10))
|
|
|
|
else:
|
|
|
|
raise OSError(-2) # ENOENT
|
|
|
|
def open(self, path, mode):
|
|
|
|
return self.File()
|
|
|
|
uos.mount(_FS(), '/_')
|
|
|
|
uos.chdir('/_')
|
|
|
|
from _injected import *
|
|
|
|
uos.umount('/_')
|
|
|
|
del _injected_buf, _FS
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2014-05-03 13:14:34 -04:00
|
|
|
def main():
|
|
|
|
import argparse
|
2021-03-15 09:57:36 -04:00
|
|
|
|
|
|
|
cmd_parser = argparse.ArgumentParser(description="Run scripts on the pyboard.")
|
|
|
|
cmd_parser.add_argument(
|
|
|
|
"--device",
|
|
|
|
default="/dev/ttyACM0",
|
|
|
|
help="the serial device or the IP address of the pyboard",
|
|
|
|
)
|
|
|
|
cmd_parser.add_argument(
|
|
|
|
"-b", "--baudrate", default=115200, help="the baud rate of the serial device"
|
|
|
|
)
|
|
|
|
cmd_parser.add_argument("-u", "--user", default="micro", help="the telnet login username")
|
|
|
|
cmd_parser.add_argument("-p", "--password", default="python", help="the telnet login password")
|
|
|
|
cmd_parser.add_argument("-c", "--command", help="program passed in as string")
|
|
|
|
cmd_parser.add_argument(
|
|
|
|
"-w",
|
|
|
|
"--wait",
|
|
|
|
default=0,
|
|
|
|
type=int,
|
|
|
|
help="seconds to wait for USB connected board to become available",
|
|
|
|
)
|
|
|
|
cmd_parser.add_argument(
|
|
|
|
"--follow",
|
|
|
|
action="store_true",
|
|
|
|
help="follow the output after running the scripts [default if no scripts given]",
|
|
|
|
)
|
2021-04-23 15:26:42 -04:00
|
|
|
cmd_parser.add_argument(
|
|
|
|
"-f", "--filesystem", action="store_true", help="perform a filesystem action"
|
|
|
|
)
|
2021-03-15 09:57:36 -04:00
|
|
|
cmd_parser.add_argument("files", nargs="*", help="input files")
|
2014-05-03 13:14:34 -04:00
|
|
|
args = cmd_parser.parse_args()
|
|
|
|
|
2016-12-14 19:29:33 -05:00
|
|
|
# open the connection to the pyboard
|
|
|
|
try:
|
|
|
|
pyb = Pyboard(args.device, args.baudrate, args.user, args.password, args.wait)
|
|
|
|
except PyboardError as er:
|
|
|
|
print(er)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
# run any command or file(s)
|
2021-04-23 15:26:42 -04:00
|
|
|
if args.command is not None or args.filesystem or len(args.files):
|
2016-12-14 19:29:33 -05:00
|
|
|
# we must enter raw-REPL mode to execute commands
|
|
|
|
# this will do a soft-reset of the board
|
2014-10-26 11:39:22 -04:00
|
|
|
try:
|
2015-05-07 05:14:51 -04:00
|
|
|
pyb.enter_raw_repl()
|
2014-10-26 11:39:22 -04:00
|
|
|
except PyboardError as er:
|
|
|
|
print(er)
|
2017-04-02 13:46:32 -04:00
|
|
|
pyb.close()
|
2014-10-26 11:39:22 -04:00
|
|
|
sys.exit(1)
|
2015-07-31 00:25:43 -04:00
|
|
|
|
2016-12-14 19:29:33 -05:00
|
|
|
def execbuffer(buf):
|
|
|
|
try:
|
|
|
|
ret, ret_err = pyb.exec_raw(buf, timeout=None, data_consumer=stdout_write_bytes)
|
|
|
|
except PyboardError as er:
|
|
|
|
print(er)
|
2017-04-02 13:46:32 -04:00
|
|
|
pyb.close()
|
2016-12-14 19:29:33 -05:00
|
|
|
sys.exit(1)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
sys.exit(1)
|
|
|
|
if ret_err:
|
|
|
|
pyb.exit_raw_repl()
|
|
|
|
pyb.close()
|
|
|
|
stdout_write_bytes(ret_err)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2021-04-23 15:26:42 -04:00
|
|
|
# do filesystem commands, if given
|
|
|
|
if args.filesystem:
|
|
|
|
filesystem_command(pyb, args.files)
|
|
|
|
args.files.clear()
|
|
|
|
|
2016-12-14 19:29:33 -05:00
|
|
|
# run the command, if given
|
|
|
|
if args.command is not None:
|
2021-03-15 09:57:36 -04:00
|
|
|
execbuffer(args.command.encode("utf-8"))
|
2016-12-14 19:29:33 -05:00
|
|
|
|
|
|
|
# run any files
|
|
|
|
for filename in args.files:
|
2021-03-15 09:57:36 -04:00
|
|
|
with open(filename, "rb") as f:
|
2016-12-14 19:29:33 -05:00
|
|
|
pyfile = f.read()
|
2021-04-23 15:26:42 -04:00
|
|
|
if filename.endswith(".mpy") and pyfile[0] == ord("M"):
|
|
|
|
pyb.exec_("_injected_buf=" + repr(pyfile))
|
|
|
|
pyfile = _injected_import_hook_code
|
2016-12-14 19:29:33 -05:00
|
|
|
execbuffer(pyfile)
|
|
|
|
|
|
|
|
# exiting raw-REPL just drops to friendly-REPL mode
|
|
|
|
pyb.exit_raw_repl()
|
|
|
|
|
|
|
|
# if asked explicitly, or no files given, then follow the output
|
2021-04-23 15:26:42 -04:00
|
|
|
if args.follow or (args.command is None and not args.filesystem and len(args.files) == 0):
|
2014-10-26 11:39:22 -04:00
|
|
|
try:
|
2015-05-07 05:14:51 -04:00
|
|
|
ret, ret_err = pyb.follow(timeout=None, data_consumer=stdout_write_bytes)
|
2014-10-26 11:39:22 -04:00
|
|
|
except PyboardError as er:
|
|
|
|
print(er)
|
|
|
|
sys.exit(1)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
sys.exit(1)
|
|
|
|
if ret_err:
|
2016-12-14 19:29:33 -05:00
|
|
|
pyb.close()
|
2015-04-19 16:31:28 -04:00
|
|
|
stdout_write_bytes(ret_err)
|
2014-10-26 11:39:22 -04:00
|
|
|
sys.exit(1)
|
2014-05-03 13:14:34 -04:00
|
|
|
|
2016-12-14 19:29:33 -05:00
|
|
|
# close the connection to the pyboard
|
|
|
|
pyb.close()
|
|
|
|
|
2021-03-15 09:57:36 -04:00
|
|
|
|
2014-03-24 08:42:06 -04:00
|
|
|
if __name__ == "__main__":
|
2014-05-03 13:14:34 -04:00
|
|
|
main()
|