tools: Add general purpose tool for USRP configuration

Over the years the UHD code base got a whole bunch of tools to
control and configure devices. This is an attempt to unify these
tools into one.

Co-authored-by: Alexander Weber <alexander.weber@ni.com>
This commit is contained in:
Lars Amsel 2022-01-13 16:50:11 +01:00 committed by Aaron Rossetto
parent 5d20a5a401
commit 99ad89609b
22 changed files with 510 additions and 10 deletions

View file

@ -123,6 +123,12 @@ set(man_page_sources
usrp2_card_burner.1
)
if (ENABLE_PYTHON_API)
set(man_page_sources
usrpctl.1
)
endif(ENABLE_PYTHON_API)
########################################################################
# Setup man pages
########################################################################

View file

@ -56,5 +56,9 @@ unless stated otherwise, they will still work with this version of UHD.
\li \subpage page_octoclock
## Usrpctl
\li \subpage page_usrpctl
*/
// vim:ft=doxygen:

View file

@ -55,7 +55,7 @@ uhd_cal_tx_dc_offset(1) uhd_cal_tx_iq_balance(1)
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -55,7 +55,7 @@ uhd_cal_rx_iq_balance(1) uhd_cal_tx_iq_balance(1)
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -55,7 +55,7 @@ uhd_cal_tx_dc_offset(1) uhd_cal_rx_iq_balance(1)
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -51,7 +51,7 @@ This manual page was written by Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2015 National Instruments Corp.
Copyright (c) 2015-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -98,7 +98,7 @@ uhd_usrp_probe(1)
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2010 Ettus Research LLC
Copyright (c) 2010-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -115,7 +115,7 @@ uhd_images_downloader(1) usrp2_card_burner(1)
This manual page was written by Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2015 Ettus Research LLC
Copyright (c) 2015-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -38,7 +38,7 @@ usrp2_card_burner(1) usrp_n2xx_simple_net_burner(1) usrp_x3xx_fpga_burner(1) oct
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012,2014 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -112,7 +112,7 @@ uhd_find_devices(1)
This manual page was written by Maitland Bottoms and Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2010 Ettus Research LLC
Copyright (c) 2010-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -39,7 +39,7 @@ uhd_images_downloader(1) usrp_n2xx_simple_net_burner(1) usrp_x3xx_fpga_burner(1)
This manual page was written by Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012,2014 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -46,7 +46,7 @@ uhd_images_downloader(1) usrp2_card_burner(1) usrp_x3xx_fpga_burner(1) octoclock
This manual page was written by Nicholas Corgan
for the Debian project (but may be used by others).
.SH COPYRIGHT
Copyright (c) 2012,2014 Ettus Research LLC
Copyright (c) 2012-2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

93
host/docs/usrpctl.1 Normal file
View file

@ -0,0 +1,93 @@
.TH "usrpctl" 1 "4.0.0" UHD "User Commands"
.SH NAME
usrpctl \- USRP Hardware Driver Peripheral Configuration Tool
.SH DESCRIPTION
Report detailed information on UHD-supported Software Radio Peripherals
attached by USB, network, or embedded configuration.
Allows update and configuration of attached devices.
.LP
The UHD package is the universal hardware driver for Ettus Research products. The goal
is to provide a host driver and API for current and future Ettus Research products.
Users will be able to use the UHD driver standalone or with 3rd party applications.
.LP
Details include unit names, revision numbers, and available sensors on all attached
USRP motherboards and daughterboards.
.SH SYNOPSIS
.B usrpctl [ID] COMMAND [OPTIONS]
.SH ID
ID is the optional device argument. It is used to define a set
of USRP devices that COMMAND should be applied to. If ID is omitted
COMMAND is applied to all reachable devices.
usrpctl understands the device args argument used by other UHD
tools like uhd_find_devices.
.SH COMMAND
Is the action the tool is to take. Every command can be either applied to a
single device or a group of devices. Commands that run on a group of
devices repeat the command for every device.
- Single device commands:
- config: Read/write configuration variables (e.g., IP address)
- probe: reads extended information about the USRP
- Multi device commands:
- update: Update binaries (e.g., FPGA image)
- reset: Reset the device or parts thereof (e.g., only reset MPM)
- find: finds all available USRPs in this network
.SH OPTIONS
The options are not always mandatory. It depends on the given COMMAND.
.SH find command
The find command takes no further options. If ID is not given it scans the
system for available, supported devices and prints a list of discovered devices.
The print out is compatible to uhd_find_devices.
ID can be used to narrow down the list of discovered devices.
.SH probe command
.IP "Print a complete property tree:"
-tree
.TP
The probe command can only be applied to a single device so make sure that
ID identifies exactly one device. Without arguments it displays detailed
information about the device such as name, serial, revision numbers,
firmware version sensor information on attached motherboard and daughterboards.
.SH EXAMPLES
.TP \w'usrpctl\ 'u
.BI usrpctl\ find
find all supported devices
.TP
.BI usrpctl\ type=x300,product=X310 find
find all x310 devices
.TP
.BI usrpctl\ name=my_usrp\ find
find a device named my_usrp
.TP
.BI usrpctl\ addr=192.168.10.2\ find
find a device with the given IP.
.TP
.BI usrpctl\ addr=192.168.10.2\ probe
display device information for USRP with the given ID
.TP
.BI usprctl\ name=my_usrp\ probe\ \-tree
display property tree of device with the name my_usrp
.TP
.SH SEE ALSO
UHD documentation:
.B http://files.ettus.com/manual/
.LP
.SH COPYRIGHT
Copyright (c) 2022 Ettus Research, A National Instruments Brand
.LP
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.LP
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

72
host/docs/usrpctl.dox Normal file
View file

@ -0,0 +1,72 @@
/*! \page page_usrpctl Usrpctl
\tableofcontents
\section usrpctl_description Description
usrpctl is the central and universal tool to
query, update or configure USRP devices. The command structure is:
usrpctl $ID $COMMAND $OPTIONS
`usrpctl` will run $COMMAND on the devices identified by $ID. $OPTIONS
is dependend on the chosen command.
\section usrpctl_id $ID
$ID is the optional device argument. It is used to define a set
of USRP devices that `$COMMAND` should be applied to. If `$ID` is omitted
$COMMAND is applied to all reachable devices.
`usrpctl` understands the device args argument used by other UHD
tools like `uhd_find_devices`.
\section usrpctl_command $COMMAND
Is the action the tool is to take. Every command can be either applied to a
single device or a group of devices. Commands that run on a group of
devices repeat the command for every device.
- Single device commands:
- `config`: Read/write configuration variables (e.g., IP address)
- `probe`: reads extended information about the USRP
- Multi device commands:
- `update`: Update binaries (e.g., FPGA image)
- `reset`: Reset the device or parts thereof (e.g., only reset MPM)
- `find`: finds all available USRPs in this network
\section usrpctl_options $OPTIONS
The options depend on the chosen command. Optional arguments are prepended
with a dash, mandatory are not.
\section usrpctl_commands Available commands
\section usrpctl_find find
The find command takes no further options. If `$ID` is not given it scans the
system for available, supported devices and prints a list of discovered devices.
The print out is compatible to \ref id_identifying_cmdline "`uhd_find_devices`".
`$ID` can be used to narrow down the list of discovered devices.
Examples:
- `usrpctl find` find all supported devices
- `usrpctl type=x300,product=X310 find` find all x310 devices
- `usrpctl name=my_usrp find` find a device named my_usrp
- `usrpctl addr=192.168.10.2 find` find a device with the given IP.
\subsection usrpctl_probe probe
Arguments:
-`-tree`: print a list of the device property tree
The probe command can only be applied to a single device so make sure that
$ID identifies exactly one device. Without arguments it displays detailed
information about the device such as name, serial, revision numbers,
firmware version sensor information on attached motherboard and daughterboards.
Examples:
- `usrpctl addr=192.168.10.2 probe` display device information for USRP with the given ID
- `usprctl name=my_usrp probe -tree` display property tree of device with the name my_usrp
*/
// vim:ft=doxygen:

View file

@ -9,6 +9,7 @@ UHD Python API module
from . import types
from . import usrp
from . import usrpctl
from . import filters
from . import rfnoc
from . import dsp

View file

@ -0,0 +1,7 @@
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
from . import commands

View file

@ -0,0 +1,9 @@
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
from .command import BaseCommand
from .find import FindCommand
from .probe import ProbeCommand

View file

@ -0,0 +1,133 @@
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
import functools
import re
import subprocess
SSH_COMMAND = 'ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 {user}@{host}'
SCP_COMMAND = 'scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 {src} {user}@{host}:{dest}'
REBOOT_SCRIPT = r'''
max_timeout=600
function ssh_cmd() {{
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 {user}@{host} $1
}}
if ! {{ boot_id=$(ssh_cmd "cat /proc/sys/kernel/random/boot_id") \
&& [[ $boot_id ]]; }}; then
echo "Unable to retrieve boot ID" >&2
exit 1
fi
ssh_cmd "reboot"
timeout_at=$(( SECONDS + max_timeout ))
until new_boot_id=$(ssh_cmd "cat /proc/sys/kernel/random/boot_id") \
&& [[ $new_boot_id != "$boot_id" ]]; do
if (( SECONDS > timeout_at )); then
echo "System did not reboot within timeout" >&2
exit 1
fi
sleep 5
done
echo "System successfully rebooted" >&2
'''
class BaseCommand:
"""
Base class for all usrpctl commands
Implements some helper useful for all command classes.
"""
@classmethod
def command_name(cls):
"""
By default the command name is the class name (lowercase) without
the trailing "Command"
"""
return re.sub("(.*)Command$", r"\1", cls.__name__).lower()
@classmethod
def add_parser(cls, parser):
"""
add_parser is used to tell usrpctl argparse which command line
argument the command accepts.
"""
def _to_arg_list(self, args):
"""
Takes parsed arguments from argparse and converts it to
a flat list of key value pairs where the key is prepended with
two dashes. If value is falsy the pair is ommitted.
This is suitable to pass parsed arguments into another process.
Example: Suppose args is:
Namespace(foo='fval', bar=2, baz=None)
then this would result in this list:
['--foo', 'fval', '--bar', 2]
"""
arg_list = {f"--{key}": value for key, value in vars(args).items() if value}
return list(functools.reduce(
lambda left, right: left + right, arg_list.items())) if arg_list else []
def _external_process(self, command):
"""
Run command in an external process.
stderr is redirected into stdout and the oupput is
yielded while the process is running.
throws CalledProcessError on non zero returncode
"""
with subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True) as process:
for line in iter(process.stdout.readline, ""):
yield line.rstrip()
returncode = process.wait()
if returncode != 0:
raise subprocess.CalledProcessError(returncode, command)
def _reboot(self, user, host):
cmd = REBOOT_SCRIPT.format(user=user, host=host)
yield from self._external_process(["bash", "-c", cmd])
def _remote_cmd(self, user, host, cmd):
"""
Executes a command via SSH.
"""
ssh_cmd = SSH_COMMAND.format(user=user, host=host)
yield from self._external_process(ssh_cmd.split(' ') + [cmd])
def _remote_copy(self, user, host, src, dest):
"""
Uses scp to copy a file to a remote host
"""
cp_cmd = SCP_COMMAND.format(user=user, host=host, src=src, dest=dest)
yield from self._external_process(cp_cmd.split(' '))
def _process_output(self, line):
print(line)
def _run_commands(self, usrp, args):
print(f"{self.__class__.__name__} is not yet implemented."
f"arguments usrp: {usrp} args: {args}")
def is_multi_device_capable(self):
"""
Tell whether this command can serve multiple USRPs at once
"""
return False
def run(self, usrps, args):
"""
Run the command
"""
for usrp in usrps:
print(f'{self.command_name()} {usrp.to_string()}')
for line in self._run_commands(usrp, args):
self._process_output(line)

View file

@ -0,0 +1,43 @@
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
from .command import BaseCommand
class FindCommand(BaseCommand):
"""
Class that finds USRP devices
"""
@classmethod
def add_parser(cls, parser):
"""
Adding subparser for find command
"""
parser.add_parser(cls.command_name(),
help="print devices found using id parameter")
def is_multi_device_capable(self):
"""
Can handle multiple USRPs
"""
return True
def run(self, usrps, args):
"""
Because usrpctl issued find to build the usrps list
from the id argument the only thing left to do is
print the found devices. Print mimics the behaviour
of uhd_find_devices.
"""
for index, usrp in enumerate(usrps):
print('--------------------------------------------------')
print(f"-- UHD Device {index}")
print('--------------------------------------------------')
print('Device Address:')
for key, value in usrp.to_dict().items():
print(f" {key}: {value}")
print()
print()

View file

@ -0,0 +1,31 @@
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
from .command import BaseCommand
class ProbeCommand(BaseCommand):
"""
Command that uses uhd_usrp_probe to query device properties.
Acts on single devices only.
"""
@classmethod
def add_parser(cls, parser):
"""
Allow -tree as argument. other uhd_usrp_probe arguments
are not supported right now
"""
subparser = parser.add_parser(cls.command_name(),
help="report detailed information on USRP device")
subparser.add_argument("-tree", const="tree", action="store_const",
help="reads the device tree")
def _run_commands(self, usrp, args):
"""
probe the device
"""
yield from self._external_process(
["uhd_usrp_probe", f"--args={usrp.to_string()}"] +
self._to_arg_list(args))

View file

@ -38,6 +38,16 @@ UHD_INSTALL(PROGRAMS
DESTINATION ${RUNTIME_DIR}
COMPONENT utilities
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/usrpctl.py"
"${CMAKE_CURRENT_BINARY_DIR}/usrpctl"
)
UHD_INSTALL(PROGRAMS
${CMAKE_CURRENT_BINARY_DIR}/usrpctl
RENAME usrpctl
DESTINATION ${RUNTIME_DIR}
COMPONENT utilities
)
########################################################################
# Utilities that get installed into the share path

91
host/utils/usrpctl.py Normal file
View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Copyright (c) 2022 Ettus Research, A National Instruments Brand
SPDX-License-Identifier: GPL-3.0-or-later
"""
import argparse
import inspect
import os
import re
import sys
import uhd
import uhd.usrpctl.commands
def get_commands():
"""
Returns a dictionary of all subclasses of BaseCommand
Classes are searched in uhd.usrpctl.commands module.
Key is the command name of the command class.
Value is the command class itself.
BaseCommand is not part of the resulting dictionary
"""
return {command[1].command_name(): command[1] for command in
inspect.getmembers(uhd.usrpctl.commands, inspect.isclass)
if issubclass(command[1], uhd.usrpctl.commands.BaseCommand)
and command[1] != uhd.usrpctl.commands.BaseCommand}
def parse_args(commands):
"""
parse command line arguments and return USRPs to search for as well
as command to execute and command arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument("id", nargs="?",
help="identifies USRPs devices utilizing dev_addr_type")
parser.add_argument("-v", action="count", default=0,
help="increase verbosity level ranging from -v to -vvvvv")
subparsers = parser.add_subparsers(dest="command",
help="supported commands, detailed help with usrpctl <cmd> --help")
for command, cls in commands.items():
cls.add_parser(subparsers)
args = parser.parse_args()
script_args = argparse.Namespace()
script_args.id = args.id
script_args.v = args.v
command = args.command
del args.id
del args.v
del args.command
return (script_args, command, args)
def find_usrps(id_args):
"""
Find USRPs based on the given id_args.
"""
return uhd.find(id_args or "")
def main():
"""
Control all the USRPs!
"""
commands = get_commands()
script_args, cmd_name, cmd_args = parse_args(commands)
env = os.environ
env['UHD_LOG_CONSOLE_LEVEL'] = str(max(0, 5 - script_args.v))
usrps = find_usrps(script_args.id)
if not usrps:
print(f"No USRP found to act on (id={script_args.id})")
return 1
command = commands[cmd_name]()
if not command.is_multi_device_capable():
if len(usrps) > 1:
print(f"Found {len(usrps)} USRPs but {cmd_name} "
"can only act on single device")
return 2
return command.run(usrps, cmd_args)
if __name__ == "__main__":
sys.exit(main())