Files
workflow-flash-with-uboot/scripts/serial_command.py
T

108 خطوط
4.4 KiB
Python

"""Check an existing U-Boot prompt or configure its volatile environment."""
import argparse
import fcntl
import re
import sys
import time
import serial
# Match the complete final line, never a prompt embedded in command output.
PROMPT = re.compile(r"(?:^|\n)([^\r\n]*=>) ?$")
ERROR = re.compile(
r"Unknown command|## Error|^Error:|^Usage:|^.*(?:not defined|not found|failed|failure).*$",
re.IGNORECASE | re.MULTILINE
)
class UBoot:
def __init__(self, port):
self.port = port
self.prompt = None
def read_prompt(self, timeout):
output = ""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
data = self.port.read(self.port.in_waiting or 1)
if data:
chunk = data.decode("utf-8", errors="replace").replace("\r", "")
print(chunk, end="", flush=True)
output += chunk
match = PROMPT.search(output)
if match and (self.prompt is None or match[1] == self.prompt):
# Wait for a quiet prompt rather than a partial output line.
time.sleep(0.15)
if self.port.in_waiting:
continue
return output, match[1]
raise RuntimeError("Timed out waiting for the CLI prompt")
def detect(self):
# Observe before requesting a fresh prompt.
try:
_, candidate = self.read_prompt(3)
except RuntimeError:
candidate = None
if candidate is None:
# An idle prompt may have been printed before the port was opened.
# A newline only requests a fresh prompt; no U-Boot command is sent.
self.port.write(b"\r")
self.port.flush()
_, candidate = self.read_prompt(5)
if not candidate.endswith("=>"):
raise RuntimeError("Response is not a recognized U-Boot CLI prompt")
self.prompt = candidate
print("\nU-Boot-style CLI prompt detected: " + candidate, flush=True)
def command(self, command, expected=None, timeout=10):
print("\n>>> " + command, flush=True)
self.port.write((command + "\r").encode())
self.port.flush()
output, _ = self.read_prompt(timeout)
# Query immediately, before another command changes the return status.
self.port.write(b"echo __UBOOT_STATUS_${?}__\r")
self.port.flush()
status, _ = self.read_prompt(5)
codes = re.findall(r"(?m)^__UBOOT_STATUS_(\d+)__$", status)
if codes != ["0"]:
raise RuntimeError("Command failed or its exit status could not be verified: " + command)
if ERROR.search(output):
raise RuntimeError("Error in command response: " + command)
if expected and not re.search(expected, output, re.MULTILINE):
raise RuntimeError("Expected response missing: " + command)
print("\nVerified: " + command, flush=True)
return output
def main():
parser = argparse.ArgumentParser()
parser.add_argument("mode", choices=("check", "configure"), default="check", nargs="?")
parser.add_argument("--port", default="/dev/ttyUSB0")
args = parser.parse_args()
# Shared by both workflows: fail rather than interleave serial commands.
with open("/tmp/uboot-serial.lock", "w") as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
with serial.Serial(args.port, 115200, timeout=0.1, write_timeout=2, exclusive=True) as port:
cli = UBoot(port)
cli.detect()
if args.mode == "check":
return
cli.command("version", r"^U-Boot \d")
cli.command("printenv ipaddr serverip ethaddr bootcmd bootargs")
cli.command("setenv ipaddr 192.168.75.100")
cli.command("printenv ipaddr", r"^ipaddr=192\.168\.75\.100$")
cli.command("setenv serverip 192.168.75.247")
cli.command("printenv serverip", r"^serverip=192\.168\.75\.247$")
cli.command("ping 192.168.75.247", r"^host 192\.168\.75\.247 is alive$", timeout=30)
cli.command("sf probe 0:0", r"^SF: Detected .+", timeout=20)
print("\nAll commands verified; environment changes were not saved to flash.")
if __name__ == "__main__":
try:
main()
except (RuntimeError, OSError, serial.SerialException) as exc:
print("\nERROR: " + str(exc), file=sys.stderr)
sys.exit(1)