1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
/*! \page page_python Python API
UHD supports a Python API, in case the C++ or C APIs are not the right solution
for your application.
\section python_install Installing the Python API
In order to install the Python API when building UHD from source, make sure you
have the CMake variable `ENABLE_PYTHON_API` set to ON (e.g., by running
`cmake -DENABLE_PYTHON_API=ON`).
UHD requires Python header files in order to compile the Python API. On most
Linux systems, there are packages called "python3-dev" or "python3-devel" that
provide that functionality. On Windows, these headers always get installed when
using the binary installers provided on https://www.python.org/downloads/windows/.
If CMake can't find the Python headers or library, specify
the `PYTHON_INCLUDE_DIRS` and/or `PYTHON_LIBRARY` CMake variables manually.
\subsection python_install_2v3 Python 2 vs. 3
The Python API supports both Python 2 and 3, but if you have both versions
installed, CMake might require some hints which version is the desired one.
To force Python 3, UHD has a CMake variable `ENABLE_PYTHON3`. If you set it,
e.g., by running `cmake -DENABLE_PYTHON3=ON`, it will force the usage of
Python 3.
\subsection python_install_windows Installing on Windows
Static linking on is unsupported on Windows. Otherwise, compiling the Python API
on Windows is no different from other operating systems.
\subsection python_install_adv Advanced Usage Notes
UHD uses the PyBind11 library to generate its Python bindings. UHD ships its own
copy of PyBind11, in order to facilitate the access to that library, as it is
not packaged for many operating systems, but also to lock down its version. For
the purpose of experimentation, it is, however possible to replace the version
of PyBind11 shipped with UHD by overriding the `PYBIND11_INCLUDE_DIR` CMake
variable.
\section python_usage Using the Python API
The Python API mirrors the C++ API, so the C++ reference manual can be used to
understand the behaviour of the Python API as well.
Names in the Python API have been modified to follow a PEP8-compatible naming
convention, for example, uhd::usrp::multi_usrp in C++ corresponds to
uhd.usrp.MultiUSRP in Python (this makes UHD/Python code implicitly compatible
with most linters, but it also has the side-effect of hiding symbols that get
imported from the C++ domain).
The following two snippets are equivalent. First the C++ version:
~~~{.cpp}
#include <uhd/usrp/multi_usrp.hpp>
// ...
auto usrp = uhd::usrp::multi_usrp::make("type=b200");
usrp->set_rx_freq(100e6);
~~~
Now the Python version:
~~~{.py}
import uhd
# ...
usrp = uhd.usrp.MultiUSRP("type=b200")
usrp.set_rx_freq(100e6)
~~~
Not all API calls from the C++ API are also supported in the Python API, and
the Python API has some additional functions that are not available in C++, but
for the most part, the uhd::usrp::multi_usrp API is identical.
\section python_usage_oneoff One-off transmit/receive applications
A common type of Python-based SDR applications are those which produce or
consume a limited number of samples. For example, an application could receive a
second's worth of samples, then do offline processing, print the result, and
exit. For this case, convenience API calls were added to the Python API. The
following snippet is an example of how to store 1 second of samples acquired at
1 Msps:
~~~{.py}
import uhd
def recv_to_file():
"""RX samples and write to file"""
usrp = uhd.usrp.MultiUSRP("type=b200")
num_samps = 1e6
if not isinstance(args.channels, list):
args.channels = [args.channels]
samps = usrp.recv_num_samps(
1e6, # Number of samples
2.4e9, # Frequency in Hz
1e6, # Sampling rate
[0], # Receive on channel 0
80, # 80 dB of RX gain
)
samps.tofile('samples.dat')
~~~
This kind of API is particularly useful in combination with Jupyter Notebooks or
similar interactive environments.
\section python_usage_gil Thread Safety and the Python Global Interpreter Lock
From the <a href="https://wiki.python.org/moin/GlobalInterpreterLock">Python wiki page on the GIL:</a>
> In CPython, the global interpreter lock, or GIL, is a mutex that protects
> access to Python objects, preventing multiple threads from executing Python
> bytecodes at once.
During some performance-critical function calls, the UHD Python API releases the
GIL, during which Python objects have their contents modified. The functions
calls which do so are uhd::rx_streamer::recv, uhd::tx_streamer::send, and
uhd::tx_streamer::recv_async_msg. To be clear, the functions listed here violate
the expected contract set out by the GIL by accessing Python objects (from C++)
without holding the GIL. This is necessary to achieve rates similar to what the
C++ API can provide.
To this end, users must ensure that the Python objects accessed by the listed
functions are handled with care. In simple, single threaded applications, this
won't require any extra work. However, in more complicated and/or multi-
threaded applications, steps must be taken to avoid thread-unsafe behavior. For
example, if an application needs to call recv() in one thread, and access the
sample buffer from another thread, a synchronization method (ie. a mutex) must
be used to safeguard access to that buffer.
*/
// vim:ft=doxygen:
|