Skip to content

Commit 5331e3b

Browse files
committed
first initialization
0 parents  commit 5331e3b

File tree

17 files changed

+3418
-0
lines changed

17 files changed

+3418
-0
lines changed

.flake8

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[flake8]
2+
filename = *.py,*.pyx,*.pxd,*.pxi
3+
ignore = E501,E203,E225,E226,E227,E402,E741,E901,E999,W503,W504
4+
force-check = True
5+
exclude =
6+
__pycache__,
7+
.venv,

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
__pycache__/
2+
3+
dist/
4+
.venv/
5+
6+
.DS_Store
7+
.pypirc
8+
9+
*.egg-info

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 GvozdevLeonid
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
include python_bladerf/pylibbladerf/pybladerf.pyx
2+
include README.md
3+
include pyproject.toml

README.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# python_bladerf
2+
3+
python_bladerf is a cython wrapper for bladerf (https://github.com/Nuand/bladeRF). It also contains some additional tools.
4+
5+
You can install this library using
6+
```
7+
pip install python_bladerf
8+
```
9+
Or assemble it manually using the following steps:
10+
11+
In order to build the library you need to go to the python_bladerf directory
12+
```
13+
cd python_bladerf
14+
```
15+
call
16+
```
17+
python setup.py build_ext --inplace.
18+
```
19+
If the build fails, you will need to specify the paths for the libusb library.
20+
```
21+
CFLAGS="-I/path to libusb.h -I/path to libbladeRF.h" \
22+
LDFLAGS="-L/path to libusb-1.0.so -L/path to libBladeRF.so" \
23+
python setup.py build_ext --inplace
24+
```
25+
26+
## Requirements:
27+
* libusb-1.0 (https://github.com/libusb/libusb)
28+
* libBladeRF (https://github.com/Nuand/bladeRF)
29+
* Cython==0.29.36
30+
* Numpy>=1.26
31+
* Scipy (optional, for faster work)
32+
* pyFFTW (optional, for faster work)
33+
34+
## bladerf:
35+
Almost all the functionality of the standard library is implemented. Some features will be added later. (async recieve and transmit).
36+
37+
## pybladerf tools:
38+
* pybladerf_info.py - Reading information about found devices.
39+
* pybladerf_sweep.py - Possibility to get extended range fft ( same as hackrf_sweep)
40+
41+
## usage
42+
```
43+
usage: python_bladerf [-h] {info, sweep} ...
44+
45+
python_bladerf is a Python wrapper for libbladerf. It also contains some additional tools.
46+
47+
options:
48+
-h, --help show this help message and exit
49+
50+
Available commands:
51+
{info,sweep}
52+
info Read device information from Bladerf such as serial number and FPGA version.
53+
sweep a command-line spectrum analyzer.
54+
```
55+
```
56+
usage: python_bladerf info [-h] [-f] [-s]
57+
58+
options:
59+
-h, --help show this help message and exit
60+
-f, --full show full info
61+
-i, --device_identifiers
62+
show only founded device_identifiers
63+
```
64+
```
65+
usage: python_bladerf sweep [-h] [-d] [-f] [-g] [-w] [-ch] [-1] [-N] [-o] [-B] [-s] [-SR] [-BW] -[FIR] [-r]
66+
67+
options:
68+
-h, --help show this help message and exit
69+
-d device_identifier. device identifier of desired BladeRF
70+
-f freq_min:freq_max. minimum and maximum frequencies in MHz start:stop. Default is 71:5999
71+
-g gain_db. RX gain, -15 - 60dB, 1dB steps
72+
-w bin_width. FFT bin width (frequency resolution) in Hz, 245-30000000
73+
-ch RX channel. which channel to use (0, 1). Default is 0
74+
-1 one shot mode. If specified = Enable
75+
-N num_sweeps. Number of sweeps to perform
76+
-o oversample. If specified = Enable
77+
-B binary output. If specified = Enable
78+
-s sweep style ("L" - LINEAR, "I" - INTERLEAVED). Default is INTERLEAVED
79+
-SR sample rate in Hz (0.5 MHz - 122 MHz). Default is 57. To use a sample rate higher than 61, specify oversample
80+
-BW bandwidth in Hz (0.2 MHz - 56 MHz). Default is 56000000
81+
-FIR RFIC RX FIR filter ("1" - Enable, "0" - Disable). Default is Disable
82+
-r filename. output file
83+
```
84+
## Note
85+
This library probably can work on android. To do this, go to the android directory and download two recipes for p4a.
86+
## Examples
87+
Examples will be added later.

android/libusb/__init__.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from pythonforandroid.util import current_directory
2+
from pythonforandroid.recipe import NDKRecipe
3+
from pythonforandroid.logger import shprint
4+
from os.path import join
5+
import shutil
6+
import sh
7+
8+
9+
class LibusbRecipe(NDKRecipe):
10+
version = '1.0.26'
11+
url = 'https://github.com/libusb/libusb/archive/refs/tags/v{version}.tar.gz'
12+
site_packages_name = 'libusb'
13+
name = 'libusb'
14+
generated_libraries = ['libusb1.0.so']
15+
16+
def should_build(self, arch):
17+
return True
18+
19+
def get_recipe_env(self, arch):
20+
env = super().get_recipe_env(arch)
21+
22+
env['LOCAL_C_INCLUDES'] = ' $(LIBUSB_ROOT_ABS)'
23+
env['LOCAL_SHARED_LIBRARIES'] = ' libusb1.0'
24+
return env
25+
26+
def get_lib_dir(self, arch):
27+
return join(self.get_build_dir(arch.arch), 'android', 'obj', 'local', arch.arch)
28+
29+
def get_jni_dir(self, arch):
30+
return join(self.get_build_dir(arch.arch), 'android', 'jni')
31+
32+
def build_arch(self, arch, *extra_args):
33+
env = self.get_recipe_env(arch)
34+
with current_directory(self.get_build_dir(arch.arch)):
35+
shprint(
36+
sh.Command(join(self.ctx.ndk_dir, "ndk-build")),
37+
'NDK_PROJECT_PATH=' + self.get_build_dir(arch.arch) + '/android',
38+
'NDK='+self.ctx.ndk_dir,
39+
'APP_PLATFORM=android-' + str(self.ctx.ndk_api),
40+
'APP_ABI=' + arch.arch,
41+
*extra_args, _env=env
42+
)
43+
44+
shutil.copyfile(join(self.get_build_dir(arch.arch), 'android', 'libs', arch.arch, 'libusb1.0.so'), join(self.ctx.get_libs_dir(arch.arch), 'libusb1.0.so'))
45+
46+
47+
recipe = LibusbRecipe()

android/python_bladerf/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from pythonforandroid.recipe import CythonRecipe # type: ignore
2+
from pythonforandroid.recipe import Recipe # type: ignore
3+
import shutil
4+
import os
5+
6+
7+
class PythonBladerfRecipe(CythonRecipe):
8+
version = '1.0.0'
9+
url = 'https://github.com/GvozdevLeonid/python_bladerf/releases/download/v.{version}/python_bladerf-{version}.tar.gz'
10+
depends = ['python3', 'setuptools', 'numpy', 'libusb', 'libbladeRF']
11+
site_packages_name = 'python_bladerf'
12+
name = 'python_bladerf'
13+
14+
def get_recipe_env(self, arch):
15+
env = super().get_recipe_env(arch)
16+
17+
libusb_recipe = Recipe.get_recipe('libusb', arch)
18+
libusb_h_dir = os.path.join(libusb_recipe.get_build_dir(arch), 'libusb')
19+
libusb_so_dir = libusb_recipe.get_lib_dir(arch)
20+
21+
libbladeRF_recipe = Recipe.get_recipe('libbladeRF', arch)
22+
libbladeRF_h_dir = os.path.join(libbladeRF_recipe.get_build_dir(arch), 'libbladeRF')
23+
libbladeRF_so_dir = libbladeRF_recipe.get_lib_dir(arch)
24+
25+
env['CFLAGS'] += ' -I' + libusb_h_dir + ' -I' + libbladeRF_h_dir
26+
env['LDFLAGS'] += ' -L' + libusb_so_dir + ' -L' + libbladeRF_so_dir
27+
28+
return env
29+
30+
def postbuild_arch(self, arch):
31+
super().postbuild_arch(arch)
32+
33+
python_bladerf_dir = os.path.join(self.ctx.get_python_install_dir(arch.arch), 'python_bladerf')
34+
os.makedirs(python_bladerf_dir, exist_ok=True)
35+
try:
36+
shutil.move(os.path.join(self.ctx.get_python_install_dir(arch.arch), 'pylibbladerf'), os.path.join(python_bladerf_dir, 'pylibbladerf'))
37+
shutil.move(os.path.join(self.ctx.get_python_install_dir(arch.arch), 'pybladerf_tools'), os.path.join(python_bladerf_dir, 'pybladerf_tools'))
38+
39+
shutil.copy(os.path.join(self.get_build_dir(arch.arch), '__init__.py'), python_bladerf_dir)
40+
shutil.copy(os.path.join(self.get_build_dir(arch.arch), '__main__.py'), python_bladerf_dir)
41+
except FileNotFoundError:
42+
pass
43+
44+
45+
recipe = PythonBladerfRecipe()

pyproject.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[project]
2+
name = "python_bladerf"
3+
version = "1.0.0"
4+
authors = [
5+
{name="Leonid Gvozdev", email="[email protected]"},
6+
]
7+
description = "This is a wrapper for bladerf."
8+
readme = "README.md"
9+
requires-python = ">=3.9"
10+
classifiers = [
11+
"Programming Language :: Python :: 3",
12+
"License :: OSI Approved :: MIT License",
13+
"Operating System :: MacOS",
14+
"Operating System :: POSIX :: Linux",
15+
"Operating System :: Android",
16+
]
17+
18+
[project.urls]
19+
"Homepage" = "https://github.com/GvozdevLeonid/python_bladerf"
20+
"Bug Tracker" = "https://github.com/GvozdevLeonid/python_bladerf/issues"
21+
22+
[build-system]
23+
requires = [
24+
"setuptools",
25+
"wheel",
26+
"numpy"
27+
]
28+
29+
[project.scripts]
30+
python_bladerf= "python_bladerf.__main__:main"

python_bladerf/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
__version__ = '1.0.0'
2+
3+
from python_bladerf.pylibbladerf import pybladerf # noqa F401
4+
from python_bladerf.pybladerf_tools import pybladerf_sweep # noqa F401

python_bladerf/__main__.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from .pybladerf_tools import (
2+
pybladerf_sweep,
3+
pybladerf_info,
4+
)
5+
from .pylibbladerf import pybladerf
6+
import argparse
7+
import sys
8+
9+
10+
def main():
11+
parser = argparse.ArgumentParser(
12+
description="python_bladerf is a Python wrapper for libbladerf. It also contains some additional tools.",
13+
usage="python_bladerf [-h] {info, sweep} ..."
14+
)
15+
subparsers = parser.add_subparsers(dest="command", title="Available commands")
16+
subparsers.required = True
17+
pybladerf_info_parser = subparsers.add_parser(
18+
'info', help='Read device information from Bladerf such as serial number and FPGA version.', usage="python_bladerf info [-h] [-f] [-s]"
19+
)
20+
pybladerf_info_parser.add_argument('-f', '--full', action='store_true', help='show full info')
21+
pybladerf_info_parser.add_argument('-i', '--device_identifiers', action='store_true', help='show only founded device_identifiers')
22+
23+
pybladerf_sweep_parser = subparsers.add_parser(
24+
'sweep', help='a command-line spectrum analyzer.', usage='python_bladerf sweep [-h] [-d] [-f] [-g] [-w] [-ch] [-1] [-N] [-o] [-B] [-s] [-SR] [-BW] -[FIR] [-r]'
25+
)
26+
pybladerf_sweep_parser.add_argument('-d', action='store', help='device_identifier. device identifier of desired BladeRF', metavar='', default='')
27+
pybladerf_sweep_parser.add_argument('-f', action='store', help='freq_min:freq_max. minimum and maximum frequencies in MHz start:stop. Default is 71:5999', metavar='', default='71:5999')
28+
pybladerf_sweep_parser.add_argument('-g', action='store', help='gain_db. RX gain, -15 - 60dB, 1dB steps', metavar='', default=20)
29+
pybladerf_sweep_parser.add_argument('-w', action='store', help='bin_width. FFT bin width (frequency resolution) in Hz, 245-30000000', metavar='', default=1000000)
30+
pybladerf_sweep_parser.add_argument('-ch', action='store', help='RX channel. which channel to use (0, 1). Default is 0', metavar='', default=0)
31+
pybladerf_sweep_parser.add_argument('-1', action='store_true', help='one shot mode. If specified = Enable')
32+
pybladerf_sweep_parser.add_argument('-N', action='store', help='num_sweeps. Number of sweeps to perform', metavar='')
33+
pybladerf_sweep_parser.add_argument('-o', action='store_true', help='oversample. If specified = Enable')
34+
pybladerf_sweep_parser.add_argument('-B', action='store_true', help='binary output. If specified = Enable')
35+
pybladerf_sweep_parser.add_argument('-s', action='store', help='sweep style ("L" - LINEAR, "I" - INTERLEAVED). Default is INTERLEAVED', metavar='', default='I')
36+
pybladerf_sweep_parser.add_argument('-SR', action='store', help='sample rate in Hz (0.5 MHz - 122 MHz). Default is 57. To use a sample rate higher than 61, specify oversample', metavar='', default=57)
37+
pybladerf_sweep_parser.add_argument('-BW', action='store', help='bandwidth in Hz (0.2 MHz - 56 MHz). Default is 56000000', metavar='', default=56.0)
38+
pybladerf_sweep_parser.add_argument('-FIR', action='store', help='RFIC RX FIR filter ("1" - Enable, "0" - Disable). Default is Disable', metavar='', default='0')
39+
pybladerf_sweep_parser.add_argument('-r', action='store', help='filename. output file', metavar='')
40+
41+
if len(sys.argv) == 1:
42+
parser.print_help()
43+
sys.exit(0)
44+
45+
args, unparsed_args = parser.parse_known_args()
46+
if args.command == 'info':
47+
if args.device_identifiers:
48+
pybladerf_info.pybladerf_device_identifiers_list_info()
49+
else:
50+
pybladerf_info.pybladerf_info()
51+
52+
elif args.command == 'sweep':
53+
frequency_range = args.f.split(':')
54+
frequencies = [71, 5999]
55+
freq_min, freq_max = None, None
56+
try:
57+
freq_min = int(frequency_range[0])
58+
except Exception:
59+
pass
60+
try:
61+
freq_max = int(frequency_range[1])
62+
except Exception:
63+
pass
64+
if freq_min is not None and freq_max is not None:
65+
frequencies = [freq_min, freq_max]
66+
67+
pybladerf_sweep.pybladerf_sweep(frequencies=frequencies,
68+
gain=int(args.g),
69+
bin_width=int(args.w),
70+
sample_rate=float(args.SR) * 1e6,
71+
bandwidth=float(args.BW) * 1e6,
72+
channel=int(args.ch),
73+
oversample=args.o,
74+
num_sweeps=int(args.N) if args.N is not None else None,
75+
binary_output=args.B,
76+
one_shot=args.__dict__.get('1'),
77+
filename=args.r,
78+
device_identifier=args.d,
79+
rxfir=pybladerf.pybladerf_rfic_rxfir.PYBLADERF_RFIC_RXFIR_DEC1 if args.FIR == '1' else (pybladerf.pybladerf_rfic_rxfir.PYBLADERF_RFIC_RXFIR_BYPASS if args.FIR == '0' else -1),
80+
sweep_style=pybladerf.pybladerf_sweep_style.PYBLADERF_SWEEP_STYLE_LINEAR if args.s == 'L' else (pybladerf.pybladerf_sweep_style.PYBLADERF_SWEEP_STYLE_INTERLEAVED if args.s == 'I' else -1),
81+
print_to_console=True)
82+
83+
84+
if __name__ == '__main__':
85+
main()

0 commit comments

Comments
 (0)