Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Bug report
description: Something isn't working as documented
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for the report. This library wraps the `hackrf-tools` binaries, so
please include tool/OS details — most issues are environment-specific.

If this involves **transmitting**, do not include instructions that would
help someone operate illegally; describe the software behavior only.
- type: textarea
id: what-happened
attributes:
label: What happened
description: What did you expect, and what happened instead?
validations:
required: true
- type: textarea
id: repro
attributes:
label: Steps to reproduce
description: The exact command or minimal code. Include `print_cmd=True` / `--print-cmd` output if relevant.
render: shell
validations:
required: true
- type: input
id: os
attributes:
label: Operating system
placeholder: "e.g. Windows 11 23H2 / Ubuntu 24.04 / macOS 14"
validations:
required: true
- type: input
id: python
attributes:
label: Python version
placeholder: "output of: python --version"
validations:
required: true
- type: input
id: hackrfpy
attributes:
label: hackrfpy version
placeholder: "output of: pip show hackrfpy | grep Version"
validations:
required: true
- type: textarea
id: tools
attributes:
label: hackrf-tools / firmware version
description: Paste the output of `hackrf_info` (redact serial numbers if you wish).
render: shell
validations:
required: false
- type: dropdown
id: hardware
attributes:
label: Was a HackRF board connected?
options:
- "Yes, a real board was connected"
- "No, this reproduces without hardware"
- "Not sure"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs / traceback
description: Full traceback or stderr output. Run with `verbose=True` / `-v` if possible.
render: shell
11 changes: 11 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Official HackRF documentation
url: https://hackrf.readthedocs.io/
about: Authoritative reference for device behavior, valid ranges, and TX safety. Check here first.
- name: HackRF firmware & tools (Great Scott Gadgets)
url: https://github.com/greatscottgadgets/hackrf
about: Report firmware, libhackrf, or hackrf-tools issues upstream — not here.
- name: Question / usage help
url: https://github.com/LC-Linkous/hackRF_python/discussions
about: For "how do I..." questions, open a Discussion rather than an issue.
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Feature request
description: Suggest a new capability or improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Before filing: **signal processing (demod, FFTs, waterfalls) is out of
scope** for this repo — it stops at delivering `complex64`. See
`project_summary.md` for the split with the planned DSP project.
- type: textarea
id: problem
attributes:
label: Problem / use case
description: What are you trying to do that the library doesn't currently support?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: What would the method / CLI command / behavior look like? A sketch of the signature is helpful.
validations:
required: false
- type: dropdown
id: area
attributes:
label: Area
options:
- Receive / capture
- Sweep
- Transmit
- Info / preflight / detect
- Device management (clock/operacake/spiflash/debug)
- Decoding / file helpers / SigMF
- CLI
- Docs / examples
- Other
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Existing workarounds, or why the passthrough methods (`clock`/`operacake`/`debug`) don't cover it.
validations:
required: false
36 changes: 36 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!-- Thanks for contributing to hackrfpy. Keep PRs focused: one logical change. -->

## Summary

<!-- What does this change and why? Link the issue it closes. -->
Closes #

## Type of change

- [ ] Bug fix
- [ ] New command / feature
- [ ] Docs / examples
- [ ] Test / CI / tooling
- [ ] Refactor (no behavior change)

## How it was tested

- [ ] `uv run pytest -m "not hardware"` passes locally
- [ ] `uv run ruff check .` and `uv run ruff format --check .` pass
- [ ] Tested against **real hardware** (describe below)
- [ ] Tested with **stubs only** (no board)

<!-- If real hardware: which board, firmware, and hackrf-tools version? -->

## Checklist for new/changed commands

- [ ] Binary invocation goes through `_run(argv, mode=...)` (no direct subprocess)
- [ ] Added a command-construction test asserting the exact `hackrf_*` argv via `print_cmd`
- [ ] Added a validation-error test asserting **nothing runs** on bad input
- [ ] Any new envelope limits live in `constants.py`, not hard-coded in the method
- [ ] Process-lifecycle changes covered with the cross-platform stub factory (`tests/conftest.py`)
- [ ] README Method Reference updated; example added under `examples/` if it's a public method

## Notes for reviewers

<!-- Anything tricky, follow-ups intentionally left out, etc. -->
78 changes: 78 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Tests

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

# Cancel superseded runs on the same ref (saves CI minutes on rapid pushes).
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
# The installable project lives in the hackrfpy/ subdirectory (the one with
# pyproject.toml + the pytest config + the `hardware` marker).
working-directory: hackrfpy

jobs:
test:
name: pytest (${{ matrix.os }}, py${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# windows-latest is first because it is the platform this library
# targets; the cross-platform lifecycle stubs must pass there.
os: [windows-latest, ubuntu-latest, macos-latest]
python-version: ["3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: ${{ matrix.python-version }}

- name: Sync environment (numpy + dev group, editable install)
run: uv sync

- name: Run test suite (hardware tests deselected)
run: uv run pytest -m "not hardware" --cov=hackrfpy --cov-report=xml --cov-report=term-missing

- name: Upload coverage to Codecov
# Optional: only runs once linked at https://codecov.io. Tokenless for
# public repos. Safe to leave in before linking - it just no-ops/soft-fails.
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
uses: codecov/codecov-action@v5
with:
files: hackrfpy/coverage.xml
fail_ci_if_error: false

lint:
name: ruff + mypy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true
python-version: "3.12"

- name: Sync environment
run: uv sync

- name: Ruff lint
run: uv run ruff check .

- name: Mypy
# Blocking. The package ships a py.typed marker, which promises
# downstream type-checkers that these annotations are real; this gate is
# what keeps that promise true.
run: uv run mypy
115 changes: 115 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Changelog

All notable changes to hackrfpy are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

Work on the current development branch. Entries move to a versioned section on
release.

### Added
- Type annotations across the entire shipped package, and `mypy` promoted to a
blocking CI gate (`disallow_untyped_defs`). The package has always shipped a
`py.typed` marker, which tells downstream type-checkers the inline annotations
are real; previously only 1 of ~114 definitions was annotated, so that marker
was a false promise. It is now enforced.
- `HostOps` protocol (`_host.py`) making the contract between `HackRF` and the
command mixins explicit and type-checkable. The mixins call ~27 methods on
`self` that live on the host class; that dependency was previously implicit in
a comment. Runtime composition and MRO are unchanged.
- Continuous integration: GitHub Actions workflow running the test suite across
Windows, Linux, and macOS on Python 3.11-3.13, plus a ruff + mypy lint job.
- CLI test suite covering argument parsing, the mode state file, preset
resolution, `info`/`detect`/`doctor`, `rx`/`tx`/`sweep` dispatch, and the
module-level exit-code mapping. Overall coverage raised from 73% to 86%
(`cli.py` from 12% to ~98%).
- Community health files: `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`,
`SECURITY.md`, issue templates, and a pull request template.

### Changed
- Library diagnostics now go through the standard `logging` module instead of
`print()`. Records are emitted on the `hackrfpy` logger: warnings at
`WARNING`, verbose progress messages at `INFO`. A consumer can now route,
reformat, or silence hackrfpy's output like any other library.
- **Diagnostics no longer touch stdout.** `print_message` previously wrote to
stdout, so `hrf sweep -v > out.csv` prepended `[*] mode: rx` into the CSV.
stdout is now reserved for data (sweep CSV, IQ on `-r -`) and explicitly
requested output (`--print-cmd`). Diagnostics go to stderr.
- Console behavior is unchanged for scripts and the CLI: warnings still appear
with no setup at all, and `verbose=True` / `-v` still prints progress. If the
host application has configured logging, hackrfpy stays out of the way and
simply propagates records to it.
- Packaging classifiers: removed the contradictory `Operating System ::
OS Independent` (the library shells out to the Windows `hackrf-tools`
binaries and Linux/macOS operation is unverified), leaving `Operating System
:: Microsoft :: Windows`. Development status raised from `3 - Alpha` to
`5 - Production/Stable` to match the 1.0.0 release.
- Ruff configuration added (`line-length = 100`, `select = ["E", "F", "W"]`), and
the codebase made lint-clean so the CI lint job is meaningful.

### Fixed
- The `atexit` backstop never reaped an orphaned `PersistentReceiver`.
`PersistentReceiver` registers itself in the live-handle registry, whose
shutdown hook calls `if h.is_alive(): h.stop()` -- but `is_alive()` did not
exist on the class, so the resulting `AttributeError` was swallowed by the
hook's bare `except Exception` and the receiver was silently left running.
Found by the typing pass; `is_alive()` added and pinned with a regression test.
- Reading from a closed `PersistentReceiver` raised a bare
`TypeError: 'NoneType' object is not iterable` instead of a typed error; it now
raises `HackRFDeviceError` with an actionable message.
- `sweep_stream(..., print_cmd=True)` would hand `StreamCtx` a `None` and crash;
it now raises `HackRFValueError` pointing at `sweep(..., print_cmd=True)`.
- Unbounded memory growth on long-lived RX/TX handles: `_Process` drained child
stdout/stderr into lists that were never trimmed, so an open-ended capture or
repeat transmit retained every per-second stats line for the life of the
process. Both drain paths now share a 64 KB cap (`_DRAIN_CAP`).
- `transmit()` now verifies the source file exists *before* arming TX and
spawning `hackrf_transfer`, raising `HackRFEnvironmentError` instead of
failing with a generic non-zero exit from the tool. The TX-mode gate is still
checked first, and `print_cmd` dry runs skip the check.
- SigMF sidecars now declare the `hackrf` namespace in `core:extensions`.
Previously the `hackrf:*` gain keys were written without declaring the
extension, which strict SigMF validators reject.
- Removed unreachable dead code in `core.py` (an orphaned `return load_iq(...)`
after a `return`, referencing three undefined names).
- The test suite no longer writes a stray `capture.sigmf-meta` into the working
directory on every run.
- `CITATION.cff` version and release date corrected to `1.0.0` / `2026-06-16`,
aligning the citation metadata with `pyproject.toml` and the tagged release.

<!--
Roadmap for this branch (append entries above as each lands):
- Added: type annotations across the public API; mypy promoted to a CI gate
(makes the shipped py.typed marker accurate).
- CI: --cov-fail-under=85; confirm the Windows leg exercises the CTRL_BREAK
lifecycle path.
- Docs: note that a single HackRF instance is not safe to share across
threads (deferred pending broader testing).
-->

## [1.0.0] - 2026-06-16

Initial public release.

### Added
- Python wrapper and non-GUI command-line tool (`hrf`) for the HackRF One,
driving the `hackrf-tools` binaries directly (no libhackrf bindings).
- Receive: bounded and streaming capture, with decode to `complex64` and
self-describing SigMF (`.sigmf-meta`) sidecars.
- Spectrum sweep with streaming CSV parsing.
- Transmit: file playback and constant-wave source, guarded by an explicit
operating-mode gate (transmit refuses unless the instance is switched to TX
mode, which emits a one-time safety banner) and a TX gain ceiling.
- Device management passthroughs (clock, Opera Cake, SPI flash, debug) and
preflight `info` / `detect` / `doctor` helpers.
- Cross-platform process lifecycle handling with best-effort clean interrupt
(SIGINT on POSIX, CTRL_BREAK on Windows) and an atexit backstop for live
RX/TX handles.
- Validation layer with hard-range checks, gain snapping to real device steps,
and a parameter readback (`last_params`) reflecting the values actually used.

[Unreleased]: https://github.com/LC-Linkous/hackRF_python/compare/V1.0.0...HEAD
[1.0.0]: https://github.com/LC-Linkous/hackRF_python/releases/tag/V1.0.0
6 changes: 3 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ authors:
- family-names: "Linkous"
given-names: "Lauren"
orcid: "https://orcid.org/0000-0001-9945-5125"
version: "0.1.0"
date-released: "2026-06-15"
version: "1.0.1"
date-released: "2026-06-16"
license: GPL-2.0-or-later
repository-code: "https://github.com/LC-Linkous/hackRF_python"
url: "https://github.com/LC-Linkous/hackRF_python"
Expand All @@ -26,4 +26,4 @@ keywords:
- rf
- spectrum
- sigmf
- instrumentation
- instrumentation
Loading
Loading