Skip to content

Commit 647e72c

Browse files
committed
tools/pyboard: Add "exec" and "execpty" pseudo-devices support.
This allows to execute a command and communicate with its stdin/stdout via pipes ("exec") or with command-created pseudo-terminal ("execpty"), to emulate serial access. Immediate usecase is controlling a QEMU process which emulates board's serial via normal console, but it could be used e.g. with helper binaries to access real board over other hadware protocols, etc. An example of device specification for these cases is: --device exec:../zephyr/qemu.sh --device execpty:../zephyr/qemu2.sh Where qemu.sh contains long-long qemu startup line, or calls another command. There's a special support in this patch for running the command in a new terminal session, to support shell wrappers like that (without new terminal session, only wrapper script would be terminated, but its child processes would continue to run).
1 parent 58168c8 commit 647e72c

1 file changed

Lines changed: 84 additions & 1 deletion

File tree

tools/pyboard.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import sys
4141
import time
42+
import os
4243

4344
try:
4445
stdout = sys.stdout.buffer
@@ -116,9 +117,91 @@ def inWaiting(self):
116117
else:
117118
return n_waiting
118119

120+
121+
class ProcessToSerial:
122+
"Execute a process and emulate serial connection using its stdin/stdout."
123+
124+
def __init__(self, cmd):
125+
import subprocess
126+
self.subp = subprocess.Popen(cmd.split(), bufsize=0, shell=True, preexec_fn=os.setsid,
127+
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
128+
129+
# Initially was implemented with selectors, but that adds Python3
130+
# dependency. However, there can be race conditions communicating
131+
# with a particular child process (like QEMU), and selectors may
132+
# still work better in that case, so left inplace for now.
133+
#
134+
#import selectors
135+
#self.sel = selectors.DefaultSelector()
136+
#self.sel.register(self.subp.stdout, selectors.EVENT_READ)
137+
138+
import select
139+
self.poll = select.poll()
140+
self.poll.register(self.subp.stdout.fileno())
141+
142+
def close(self):
143+
import signal
144+
os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
145+
146+
def read(self, size=1):
147+
data = b""
148+
while len(data) < size:
149+
data += self.subp.stdout.read(size - len(data))
150+
return data
151+
152+
def write(self, data):
153+
self.subp.stdin.write(data)
154+
return len(data)
155+
156+
def inWaiting(self):
157+
#res = self.sel.select(0)
158+
res = self.poll.poll(0)
159+
if res:
160+
return 1
161+
return 0
162+
163+
164+
class ProcessPtyToTerminal:
165+
"""Execute a process which creates a PTY and prints slave PTY as
166+
first line of its output, and emulate serial connection using
167+
this PTY."""
168+
169+
def __init__(self, cmd):
170+
import subprocess
171+
import re
172+
import serial
173+
self.subp = subprocess.Popen(cmd.split(), bufsize=0, shell=True, preexec_fn=os.setsid,
174+
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
175+
pty_line = self.subp.stderr.readline().decode("utf-8")
176+
m = re.search(r"/dev/pts/[0-9]+", pty_line)
177+
if not m:
178+
print("Error: unable to find PTY device in startup line:", pty_line)
179+
self.close()
180+
sys.exit(1)
181+
pty = m.group()
182+
self.ser = serial.Serial(pty, interCharTimeout=1)
183+
184+
def close(self):
185+
import signal
186+
os.killpg(os.getpgid(self.subp.pid), signal.SIGTERM)
187+
188+
def read(self, size=1):
189+
return self.ser.read(size)
190+
191+
def write(self, data):
192+
return self.ser.write(data)
193+
194+
def inWaiting(self):
195+
return self.ser.inWaiting()
196+
197+
119198
class Pyboard:
120199
def __init__(self, device, baudrate=115200, user='micro', password='python', wait=0):
121-
if device and device[0].isdigit() and device[-1].isdigit() and device.count('.') == 3:
200+
if device.startswith("exec:"):
201+
self.serial = ProcessToSerial(device[len("exec:"):])
202+
elif device.startswith("execpty:"):
203+
self.serial = ProcessPtyToTerminal(device[len("qemupty:"):])
204+
elif device and device[0].isdigit() and device[-1].isdigit() and device.count('.') == 3:
122205
# device looks like an IP address
123206
self.serial = TelnetToSerial(device, user, password, read_timeout=10)
124207
else:

0 commit comments

Comments
 (0)