hackrfpy 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hackrfpy/exceptions.py ADDED
@@ -0,0 +1,43 @@
1
+ #! /usr/bin/python3
2
+
3
+ ##--------------------------------------------------------------------\
4
+ # hackrfpy
5
+ # 'src/hackrfpy/exceptions.py'
6
+ #
7
+ # Typed exception hierarchy. The API layer (core + mixins) RAISES these;
8
+ # the CLI layer catches the base class once in main() and pretty-prints to
9
+ # stderr with a clean exit code. Importing scripts get real, catchable
10
+ # exceptions instead of sentinel return values.
11
+ #
12
+ # Author(s): <you>
13
+ ##--------------------------------------------------------------------\
14
+
15
+
16
+ class HackRFError(Exception):
17
+ """Base for everything this library raises. Catch this to catch all."""
18
+ exit_code = 1
19
+
20
+
21
+ class HackRFValueError(HackRFError):
22
+ """A parameter is outside the operating envelope (freq, sample rate, gain).
23
+ Raised by the validation layer when reject-by-default fires and --force
24
+ was not given."""
25
+ exit_code = 2
26
+
27
+
28
+ class HackRFModeError(HackRFError):
29
+ """An operation was attempted in the wrong mode -- e.g. transmit() while
30
+ the device is in RX mode. Switch modes first (the deliberate TX gate)."""
31
+ exit_code = 3
32
+
33
+
34
+ class HackRFDeviceError(HackRFError):
35
+ """The device or its tooling failed: a hackrf_* binary is missing, no board
36
+ was found, or a subprocess exited non-zero."""
37
+ exit_code = 4
38
+
39
+
40
+ class HackRFEnvironmentError(HackRFError):
41
+ """The host environment can't support the request: insufficient disk for a
42
+ capture, missing permissions, etc. Surfaced mostly by doctor/estimator."""
43
+ exit_code = 5
hackrfpy/presets.py ADDED
@@ -0,0 +1,68 @@
1
+ #! /usr/bin/python3
2
+
3
+ ##--------------------------------------------------------------------\
4
+ # hackrfpy
5
+ # 'src/hackrfpy/presets.py'
6
+ #
7
+ # Band / frequency presets. These are a USER CONVENIENCE for scanning and
8
+ # selecting -- named center/edge frequencies and sensible sample rates so you
9
+ # don't memorize numbers. They are NOT a TX gate; transmit uses the full
10
+ # device range regardless of presets.
11
+ #
12
+ # Built-ins can be extended/overridden from a user TOML
13
+ # (~/.config/hackrfpy/presets.toml) via load_presets().
14
+ #
15
+ # Author(s): <you>
16
+ ##--------------------------------------------------------------------\
17
+
18
+ import os
19
+
20
+ try:
21
+ import tomllib # Python 3.11+
22
+ except ModuleNotFoundError: # pragma: no cover
23
+ tomllib = None
24
+
25
+ # Each preset: center or (f_min, f_max) for sweeps, plus a default sample rate.
26
+ BUILTIN_PRESETS = {
27
+ "fm": {"f_min": 88_000_000, "f_max": 108_000_000, "sample_rate": 10_000_000,
28
+ "desc": "FM broadcast band"},
29
+ "airband": {"f_min": 118_000_000, "f_max": 137_000_000, "sample_rate": 8_000_000,
30
+ "desc": "VHF airband (AM voice)"},
31
+ "ads-b": {"center": 1_090_000_000, "sample_rate": 8_000_000,
32
+ "desc": "ADS-B (1090 MHz)"},
33
+ "ism-433": {"center": 433_920_000, "sample_rate": 2_000_000,
34
+ "desc": "433 MHz ISM"},
35
+ "ism-915": {"center": 915_000_000, "sample_rate": 8_000_000,
36
+ "desc": "915 MHz ISM (US)"},
37
+ "noaa-apt": {"f_min": 137_000_000, "f_max": 138_000_000, "sample_rate": 2_000_000,
38
+ "desc": "NOAA weather satellites"},
39
+ "gps-l1": {"center": 1_575_420_000, "sample_rate": 10_000_000,
40
+ "desc": "GPS L1"},
41
+ }
42
+
43
+
44
+ def _config_path():
45
+ base = os.environ.get("XDG_CONFIG_HOME",
46
+ os.path.join(os.path.expanduser("~"), ".config"))
47
+ return os.path.join(base, "hackrfpy", "presets.toml")
48
+
49
+
50
+ def load_presets():
51
+ # Built-ins overlaid with the user's TOML (user wins on key collisions).
52
+ presets = {k: dict(v) for k, v in BUILTIN_PRESETS.items()}
53
+ path = _config_path()
54
+ if tomllib and os.path.isfile(path):
55
+ with open(path, "rb") as f:
56
+ user = tomllib.load(f)
57
+ for name, cfg in user.get("presets", {}).items():
58
+ presets[name] = cfg
59
+ return presets
60
+
61
+
62
+ def get_preset(name):
63
+ presets = load_presets()
64
+ if name not in presets:
65
+ from .exceptions import HackRFValueError
66
+ raise HackRFValueError(
67
+ f"unknown preset {name!r}; known: {', '.join(sorted(presets))}")
68
+ return presets[name]
hackrfpy/py.typed ADDED
File without changes
hackrfpy/sigmf.py ADDED
@@ -0,0 +1,72 @@
1
+ #! /usr/bin/python3
2
+
3
+ ##--------------------------------------------------------------------\
4
+ # hackrfpy
5
+ # 'src/hackrfpy/sigmf.py'
6
+ #
7
+ # Minimal SigMF metadata writer. Writes a '<capture>.sigmf-meta' sidecar so
8
+ # recordings are self-describing instead of headerless int8 blobs. Follows
9
+ # the SigMF core namespace; HackRF native format is interleaved signed 8-bit
10
+ # I/Q -> datatype "ci8".
11
+ #
12
+ # Author(s): <you>
13
+ ##--------------------------------------------------------------------\
14
+
15
+ import json
16
+ import os
17
+ from datetime import datetime, timezone
18
+
19
+
20
+ def write_sigmf_meta(data_path, freq, sample_rate, *, lna=None, vga=None,
21
+ amp=None, datatype="ci8", extra=None):
22
+ # Sidecar path: foo.iq -> foo.sigmf-meta
23
+ base, _ = os.path.splitext(data_path)
24
+ meta_path = base + ".sigmf-meta"
25
+
26
+ hw = "HackRF One"
27
+ annotations_gains = {}
28
+ if lna is not None:
29
+ annotations_gains["hackrf:lna_gain_db"] = lna
30
+ if vga is not None:
31
+ annotations_gains["hackrf:vga_gain_db"] = vga
32
+ if amp is not None:
33
+ annotations_gains["hackrf:amp_enabled"] = bool(amp)
34
+
35
+ meta = {
36
+ "global": {
37
+ "core:datatype": datatype,
38
+ "core:sample_rate": float(sample_rate),
39
+ "core:hw": hw,
40
+ "core:version": "1.0.0",
41
+ "core:recorder": "hackrfpy",
42
+ **annotations_gains,
43
+ },
44
+ "captures": [
45
+ {
46
+ "core:sample_start": 0,
47
+ "core:frequency": float(freq),
48
+ "core:datetime": datetime.now(timezone.utc).isoformat(),
49
+ }
50
+ ],
51
+ "annotations": [],
52
+ }
53
+ if extra:
54
+ meta["global"].update(extra)
55
+
56
+ with open(meta_path, "w") as f:
57
+ json.dump(meta, f, indent=2)
58
+ return meta_path
59
+
60
+
61
+ def read_sigmf_meta(path: str) -> dict:
62
+ """Read a .sigmf-meta sidecar back into a dict.
63
+
64
+ Accepts either the meta path itself or the data path (foo.iq is mapped
65
+ to foo.sigmf-meta). The reader half of write_sigmf_meta so consuming
66
+ code can recover frequency / sample rate / gains without re-parsing.
67
+ """
68
+ if not path.endswith(".sigmf-meta"):
69
+ base, _ = os.path.splitext(path)
70
+ path = base + ".sigmf-meta"
71
+ with open(path) as f:
72
+ return json.load(f)
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.4
2
+ Name: hackrfpy
3
+ Version: 1.0.0
4
+ Summary: A self-contained CLI + scriptable wrapper for the HackRF One, built on the hackrf-tools binaries (no libhackrf bindings required).
5
+ Keywords: hackrf,sdr,rf,spectrum,sigmf
6
+ Author: LC-Linkous
7
+ License-Expression: GPL-2.0-or-later
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Dist: numpy>=1.26
18
+ Requires-Dist: matplotlib ; extra == 'plotting'
19
+ Requires-Python: >=3.11
20
+ Project-URL: Homepage, https://github.com/LC-Linkous/hackRF_python
21
+ Project-URL: Issues, https://github.com/LC-Linkous/hackRF_python/issues
22
+ Project-URL: Repository, https://github.com/LC-Linkous/hackRF_python
23
+ Provides-Extra: plotting
24
+ Description-Content-Type: text/markdown
25
+
26
+ # hackrfpy
27
+
28
+ **An Unofficial Python CLI + Scripting Wrapper for the HackRF One that works on Windows**
29
+
30
+ [![PyPI version](https://badge.fury.io/py/hackrfpy.svg)](https://badge.fury.io/py/hackrfpy)
31
+ [![Python versions](https://img.shields.io/pypi/pyversions/hackrfpy.svg)](https://pypi.org/project/hackrfpy/)
32
+ [![PyPI - Wheel](https://img.shields.io/pypi/wheel/hackrfpy.svg)](https://pypi.org/project/hackrfpy/)
33
+ [![Downloads](https://static.pepy.tech/badge/hackrfpy)](https://pepy.tech/project/hackrfpy)
34
+ [![License: GPL v2](https://img.shields.io/badge/License-GPL_v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
35
+
36
+ A non-GUI Python wrapper and command-line tool for the HackRF One software-defined radio. This library provides programmatic control for IQ capture, spectrum sweeps, and transmit, with self-describing SigMF recordings.
37
+
38
+ Unlike libraries that bind to `libhackrf` through C extensions, hackrfpy runs the standard `hackrf-tools` command-line binaries (`hackrf_info`, `hackrf_transfer`, `hackrf_sweep`, and the device-management tools) as subprocesses. Nothing has to be compiled, which is what makes it practical to install and run on Windows. The cost is that the `hackrf-tools` binaries are a **system** dependency you install separately; see [Installation](#installation).
39
+
40
+ This repository uses official resources and documentation but is **NOT** endorsed by Great Scott Gadgets or the HackRF project. Refer to official resources and support for product information.
41
+
42
+ ## Features
43
+
44
+ - **Device Discovery** — detect and identify connected HackRF boards, report firmware and identity
45
+ - **IQ Capture** — bounded, timed, streaming, or callback-style receive; decoded to normalized `complex64`
46
+ - **Spectrum Sweep** — collect or stream `hackrf_sweep` output across a frequency range
47
+ - **Transmit** — file playback and constant-wave test mode, behind a deliberate TX-mode gate
48
+ - **Operating Envelope** — per-parameter range checks and gain snapping against the device's real steps
49
+ - **SigMF Recordings** — self-describing `.iq` captures with metadata sidecars
50
+ - **Error Handling** — a typed exception hierarchy and verbose output options
51
+ - **CLI** — the `hrf` command-line shell over the full API
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install hackrfpy
57
+ ```
58
+
59
+ The library itself depends only on `numpy`. The plotting examples need an optional extra:
60
+
61
+ ```bash
62
+ pip install "hackrfpy[plotting]"
63
+ ```
64
+
65
+ Python 3.11+ is required.
66
+
67
+ **You also need the `hackrf-tools` binaries**, which are *not* a pip dependency — they are installed at the OS level:
68
+
69
+ - **Linux:** `sudo apt install hackrf` (or your distribution's equivalent)
70
+ - **macOS:** `brew install hackrf`
71
+ - **Windows:** the tools are published as CI build artifacts under the [Actions tab](https://github.com/greatscottgadgets/hackrf/actions) of the HackRF repo; see the main repository README for the step-by-step.
72
+
73
+ Verify the install with `hackrf_info`.
74
+
75
+ ## Quick Start
76
+
77
+ ```python
78
+ from hackrfpy import HackRF
79
+
80
+ h = HackRF()
81
+ det = h.detect()
82
+ if det["ready"]:
83
+ print(h.identify())
84
+ ```
85
+
86
+ To collect a bounded IQ capture as a normalized `complex64` array:
87
+
88
+ ```python
89
+ from hackrfpy import HackRF
90
+
91
+ h = HackRF()
92
+ iq = h.capture_array(433.92e6, 8e6, num_samples=1_000_000) # 433.92 MHz, 8 Msps
93
+ print(iq.dtype, len(iq)) # complex64, 1000000
94
+ ```
95
+
96
+ To run a single spectrum sweep:
97
+
98
+ ```python
99
+ from hackrfpy import HackRF
100
+
101
+ h = HackRF()
102
+ rows = h.sweep_collect(88e6, 108e6, num_sweeps=1) # FM broadcast band
103
+ for r in rows:
104
+ print(r["hz_low"], r["hz_high"], min(r["db"]), max(r["db"]))
105
+ ```
106
+
107
+ ## Transmitting
108
+
109
+ Transmit is gated behind an explicit mode switch, because an accidental transmit is the one operation that can damage equipment or break the law:
110
+
111
+ ```python
112
+ from hackrfpy import HackRF
113
+
114
+ h = HackRF()
115
+ h.set_mode("tx") # prints the TX-mode safety banner
116
+ h.transmit(433.92e6, 8e6, "signal.iq", txvga=20)
117
+ ```
118
+
119
+ **Transmitting is regulated.** You are responsible for operating within the law and within your equipment's limits.
120
+
121
+ ## Examples
122
+
123
+ The [main GitHub repository](https://github.com/LC-Linkous/hackRF_python) provides runnable examples, grouped by what they demonstrate.
124
+
125
+ **Getting started / device control**
126
+
127
+ - `device_explorer.py` — detect, identify, and report board capabilities (read-only)
128
+ - `capture_to_file.py` — bounded capture to a file with a SigMF sidecar, then read it back
129
+
130
+ **Acquisition**
131
+
132
+ - `persistent_capture.py` — collect many segments at one frequency from a single long-lived process
133
+ - `power_meter.py` — live dBFS power meter at one frequency via the callback API
134
+ - `scan_then_capture.py` — sweep a band, find the strongest bin, then capture there
135
+
136
+ **Sweep and plotting**
137
+
138
+ - `sweep_collect.py` — one sweep across a band, saved to CSV
139
+ - `waterfall_realtime.py` — a live, continuously updating spectrum waterfall
140
+ - `waterfall_persistent.py` — a single-frequency FFT waterfall over time
141
+
142
+ **Calibration and benchmarking**
143
+
144
+ - `calibrate.py` — derive an `offset_db` and frequency-response curve for relative-power readings
145
+ - `benchmark.py` — measure decode throughput and callback latency on your hardware
146
+
147
+ **Sample data**
148
+
149
+ - `collect_sample_data.py` — collect real IQ + sweep datasets (read-only; never transmits)
150
+
151
+ > Most plotting examples require the optional plotting dependencies:
152
+ > `pip install "hackrfpy[plotting]"`
153
+
154
+ ## Documentation
155
+
156
+ For comprehensive documentation, the full method reference, the CLI reference, and the operating envelope:
157
+
158
+ - **Library GitHub repository**: [https://github.com/LC-Linkous/hackRF_python/](https://github.com/LC-Linkous/hackRF_python/)
159
+ - **Official HackRF documentation**: [https://hackrf.readthedocs.io/](https://hackrf.readthedocs.io/) (not associated with this library)
160
+
161
+ ## Contributing
162
+
163
+ This is an unofficial community project. Contributions welcome!
164
+
165
+ - Report bugs and request features on [GitHub](https://github.com/LC-Linkous/hackRF_python)
166
+ - For device information and OFFICIAL resources, see [https://hackrf.readthedocs.io/](https://hackrf.readthedocs.io/)
167
+ - Please do **NOT** request features or report bugs to Great Scott Gadgets or the HackRF project! This is an unofficial project and they do not maintain it.
168
+
169
+ ## Citing
170
+
171
+ If you use this library in your work, citation details are in the repository's `CITATION.cff`.
172
+
173
+ ## License
174
+
175
+ GPL-2.0 — this package and the repo code is unofficial software with no warranty, offered AS-IS. Use at your own risk.
176
+
177
+ The licensing of this software does NOT take priority over the official releases and the decisions of Great Scott Gadgets, and does NOT apply to any of their products or firmware.
178
+
179
+ ## Acknowledgments
180
+
181
+ - Great Scott Gadgets and the HackRF community, who created and maintain the device and its tools
182
+ - Official HackRF documentation and resources, especially [hackrf.readthedocs.io](https://hackrf.readthedocs.io/)
183
+ - All contributors to this library, including those who have contributed code and reached out with questions
184
+
185
+ ---
186
+
187
+ **Disclaimer**: This software is unofficial and not supported by Great Scott Gadgets or the HackRF project. For official software and support, visit [hackrf.readthedocs.io](https://hackrf.readthedocs.io/). The HackRF makers do not offer tech support for this software, do not maintain it, and have no responsibility for any of the contents.
@@ -0,0 +1,22 @@
1
+ hackrfpy/__init__.py,sha256=RLgE12IvGvDVMQKOh7ZneGDcbokSCoz-3j6MoqaCqv8,698
2
+ hackrfpy/_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ hackrfpy/_commands/capture.py,sha256=zfQJK5zrA_KSjL6DmcdIeohqLJAPayD0EoXamPkvCA8,13123
4
+ hackrfpy/_commands/device.py,sha256=ENeTspf1WdzJJsjvUkcs4kwrmxnzHU865pIHF5jhiaM,9449
5
+ hackrfpy/_commands/info.py,sha256=p9J_QAsF4Zh1piUVUsb8VO2xIaLIujAfH0WWQu0hFpo,7688
6
+ hackrfpy/_commands/sweep.py,sha256=j0xvbttiP2shNZkVWDA5iiYqfM4xs-6Ldyx6X3qVYRo,10450
7
+ hackrfpy/_commands/transmit.py,sha256=cSNlMhJ-Q7YCrsmBeDYePe77wLAC06Yi8hKdOwWNv9w,5037
8
+ hackrfpy/_receiver.py,sha256=iu6Gud9We8eH4ZOR6D0EUQ9eYuzEqJsmscMY7IIHyTE,6257
9
+ hackrfpy/_stream_ctx.py,sha256=woDXoC82OP3D195T89vf6qpaQIzHF6AAl3FBFJWYt9g,940
10
+ hackrfpy/cli.py,sha256=BuTcr47Oz30YM5HrJVGwy6qWjfgRJh57voK31GJQRxA,12379
11
+ hackrfpy/comparison.md,sha256=FXMV3OxRmijJAdjNvJMIk4nbPwiqup_dAQ5oawXatyk,10981
12
+ hackrfpy/constants.py,sha256=r2hFbRqa6a2Z5DCWF5GKdfrS5edkSjP6DzbIa-crLJU,4786
13
+ hackrfpy/core.py,sha256=PMO7ISzrG2DYRsAYFa6RczNl47wULcjanfoG5VAt9Jc,33216
14
+ hackrfpy/exceptions.py,sha256=kXKHe9MPrV3CyT01pG-rkgsY1q24gKvBFV7xZh6Eh-8,1493
15
+ hackrfpy/presets.py,sha256=9F4nWlfZ61_dfwmTx6JaMxIY-L8O32FHngOFAz0zeKw,2573
16
+ hackrfpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ hackrfpy/sigmf.py,sha256=t4sk3CeuzAVog9vYgqyV2FEN7HIc26TRZl7E0lKV7r0,2275
18
+ hackrfpy-1.0.0.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
19
+ hackrfpy-1.0.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
20
+ hackrfpy-1.0.0.dist-info/entry_points.txt,sha256=Xbr4o5v7gokphyoAOXj3Dmw9DKV668ddp7U5VZ8Nzn8,43
21
+ hackrfpy-1.0.0.dist-info/METADATA,sha256=RMmEjfhJ2vbp9_R8Wqo4yfpAl76544lKCbJDm0VZN5c,8453
22
+ hackrfpy-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ hrf = hackrfpy.cli:main
3
+