mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-07-27 00:48:23 +08:00
The firmware already ships sd_bench, sd_stress, and serial_test; the suite was not exploiting them. bench/storage_stress.py joins the default sequence before log_transfer (storage health first, then the test that depends on storage). It runs sd_bench with data verification and parses write/read throughput and fsync latency into named checks with generous FAIL floors (defaults justified in the README: 100 KB/s write, 200 KB/s read, 500 ms fsync; a working-but-slow card prints a WARNING instead of flapping), then sd_stress for file churn with a short default iteration count. Both phases probe-and-skip independently: a firmware without a command or a board without an SD card records SKIP with a warning, never FAIL. bench/serial_loopback.py is a standalone operator/fixture test for manufacturing: with a TX-RX loopback jumper installed on a chosen UART, serial_test transmits a known pattern and the session summary (rx/tx/rx err counts) plus pattern-error lines decide the verdict. Not in the default sequence because it needs a physical fixture. The firmware gate's --build path now prints a bench capability report from one shared table: after reading default.px4board plus the label overlay (labels merge both, cmake/kconfig.cmake:47-54) it lists whether the image will contain simulator_sih, sd_bench, sd_stress, and serial_test and which suite tests will run or skip. For anything missing it names a sibling variant that has it (exact --target) and the exact config line, and interactively offers to append the line to the local board config, stating that the edit is local and uncommitted. All four options are 'default n' in Kconfig, so the merged .px4board lines decide presence. Signed-off-by: Ramon Roche <mrpollo@gmail.com>
146 lines
5.9 KiB
Python
Executable File
146 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
UART loopback test using the firmware's serial_test command.
|
|
|
|
Operator/fixture test: a loopback jumper (TX wired to RX) is installed on
|
|
one of the board's UARTs, then serial_test (src/systemcmds/serial_test)
|
|
transmits a known pattern and verifies its own received bytes through the
|
|
jumper. This exercises the real UART driver, DMA, and interrupt path,
|
|
which the USB-only bench tests never touch. Intended for manufacturing
|
|
end-of-line checks and for qualifying serial driver changes.
|
|
|
|
NOT part of the default suite: it needs a physical fixture. Like
|
|
usb_replug, run it manually.
|
|
|
|
The chosen UART must not be claimed by a running service (mavlink
|
|
instance, GPS driver, RC input). Pick a port whose function is disabled in
|
|
the parameters, or a dedicated test pad on the fixture.
|
|
|
|
Verified against src/systemcmds/serial_test/serial_test.c: options
|
|
-p PORT -b BAUD -o TX_SECONDS -i RX_SECONDS; the session summary line is
|
|
'<port>: count for this session: rx=N, tx=N, rx err=N'; each received
|
|
byte that does not match the expected pattern prints an
|
|
'Error, count: ...' line.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import px4bench
|
|
|
|
SUMMARY_RE = re.compile(
|
|
r'count for this session:\s*rx=(\d+),\s*tx=(\d+),\s*rx err=(\d+)')
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='UART loopback test via serial_test over the MAVLink shell '
|
|
'(requires a TX-RX jumper on the tested port).')
|
|
px4bench.add_connection_args(parser)
|
|
parser.add_argument('--device', required=True,
|
|
help="UART device ON the board to test, e.g. /dev/ttyS2 "
|
|
"(must have the loopback jumper and no service "
|
|
"using it)")
|
|
parser.add_argument('--test-baud', type=int, default=115200,
|
|
help='baud rate for the loopback run (default: %(default)s)')
|
|
parser.add_argument('--seconds', type=int, default=5,
|
|
help='transmit/receive duration (default: %(default)s)')
|
|
parser.add_argument('--no-prompt', action='store_true', default=False,
|
|
help='skip the jumper confirmation pause (fixture automation)')
|
|
parser.add_argument('--report-dir', default='bench_reports',
|
|
help='base directory for report output (default: %(default)s)')
|
|
args = parser.parse_args()
|
|
|
|
report = px4bench.Reporter('serial_loopback')
|
|
report_dir = px4bench.make_report_dir(args.report_dir, 'serial_loopback')
|
|
report.info('report dir: {}'.format(report_dir))
|
|
|
|
try:
|
|
mav = px4bench.connect(args.connection, baud=args.baudrate,
|
|
timeout=args.connect_timeout)
|
|
except (TimeoutError, OSError) as e:
|
|
report.fail('connect', str(e))
|
|
sys.exit(report.finish())
|
|
report.ok('connect', 'heartbeat from {}'.format(args.connection))
|
|
|
|
shell = px4bench.MavlinkShell(mav)
|
|
if not shell.open(timeout=5):
|
|
report.fail('shell open', 'no nsh output on SERIAL_CONTROL within 5s')
|
|
mav.close()
|
|
sys.exit(report.finish())
|
|
|
|
present, _ = px4bench.shell_command_exists(shell, 'serial_test -h')
|
|
if present is None:
|
|
report.fail('probe', 'serial_test probe stalled over the shell')
|
|
shell.close()
|
|
mav.close()
|
|
sys.exit(report.finish())
|
|
if present is False:
|
|
report.info('WARNING: serial_test not in this firmware (command not '
|
|
'found); skipping. Enable CONFIG_SYSTEMCMDS_SERIAL_TEST=y')
|
|
shell.close()
|
|
mav.close()
|
|
sys.exit(px4bench.EXIT_SKIP)
|
|
|
|
if not args.no_prompt:
|
|
print('-' * 60)
|
|
print('Install the loopback jumper now: wire TX to RX on {}.'.format(
|
|
args.device))
|
|
print('Make sure no service (mavlink, GPS, RC) is configured on that '
|
|
'port.')
|
|
print('-' * 60, flush=True)
|
|
input('Press Enter when the jumper is installed... ')
|
|
|
|
cmd = 'serial_test -p {} -b {} -o {} -i {}'.format(
|
|
args.device, args.test_baud, args.seconds, args.seconds)
|
|
timeout = args.seconds + 20
|
|
report.info('running {} (timeout {:.0f}s)'.format(cmd, timeout))
|
|
out, timed_out = shell.run(cmd, timeout=timeout)
|
|
try:
|
|
with open(os.path.join(report_dir, 'serial_test.txt'), 'w') as f:
|
|
f.write(out)
|
|
except OSError:
|
|
pass
|
|
shell.close()
|
|
mav.close()
|
|
|
|
if timed_out:
|
|
report.fail('serial_test', 'serial_test stalled (no completion within '
|
|
'{:.0f}s), output saved'.format(timeout))
|
|
sys.exit(report.finish())
|
|
|
|
if 'ERROR: Port argument required' in out or 'Cannot open' in out.replace("Can't", 'Cannot'):
|
|
report.fail('serial_test', 'could not open {} (in use or missing?): '
|
|
'see saved output'.format(args.device))
|
|
sys.exit(report.finish())
|
|
|
|
m = SUMMARY_RE.search(out)
|
|
if not m:
|
|
report.fail('serial_test summary',
|
|
'no session summary in output (port in use or wrong '
|
|
'device?); output saved')
|
|
sys.exit(report.finish())
|
|
rx, tx, err = int(m.group(1)), int(m.group(2)), int(m.group(3))
|
|
mismatches = len(re.findall(r'Error, count:', out))
|
|
|
|
report.check('tx', tx > 0, '{} bytes transmitted'.format(tx))
|
|
report.check('rx', rx > 0,
|
|
'{} bytes received through the jumper'.format(rx)
|
|
if rx > 0 else 'nothing received: jumper missing or wrong port?')
|
|
report.check('pattern errors', err == 0 and mismatches == 0,
|
|
'rx err={}, {} mismatch line(s)'.format(err, mismatches))
|
|
delta = abs(tx - rx)
|
|
tolerance = max(1024, tx // 50)
|
|
report.check('rx/tx balance', delta <= tolerance,
|
|
'tx={} rx={} delta={} (tolerance {})'.format(tx, rx, delta, tolerance))
|
|
|
|
sys.exit(report.finish())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|