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
94 changes: 67 additions & 27 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,26 @@ body:
attributes:
value: |
Thanks for the report. This library talks to real hardware over serial,
so please include device and OS details — many issues are
so please include device and firmware details — many issues are
firmware- or environment-specific.

**Two things that answer most reports before anyone asks:**

1. A log file. Turn it on before reproducing, then attach it:
```python
tsa = tinySA()
tsa.set_log_file("issue.jsonl") # UTC-stamped JSON Lines; no screen or scan data at the default level
tsa.autoconnect()
... your code ...
```
Its first line records your board, firmware build and library version;
each command line records what was sent, what the device answered,
and why the library returned an error (`"reason"`).
2. The README section **Firmware Compatibility**. If the device replied
with a `usage:` line, `name?`, or `Command timeout`, the firmware on the
device may not have (or may have changed) that command. The library
supports firmware v1.4-182 and later; `version()` shows yours.

For questions about **device behavior** (not this library), the
[official tinySA wiki](https://tinysa.org/wiki/) and the tinySA
community at groups.io are the right places — they do not support this
Expand All @@ -24,10 +41,51 @@ body:
id: repro
attributes:
label: Steps to reproduce
description: The exact minimal code. Include the command string the library sent if you have it (set_verbose(True) output helps).
description: The exact minimal code. Include the command string the library sent if you have it (the log file or set_verbose(True) output shows it).
render: python
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log file / traceback
description: Attach `issue.jsonl` (drag it into this box) or paste the relevant lines, plus the full traceback if there was one. `set_error_byte_return(True)` makes refused commands return `ERROR` instead of an empty reply.
render: shell
validations:
required: false
- type: dropdown
id: device
attributes:
label: Device model
description: The board is in `info()` and in the log's first line (ZS304 = Basic, ZS405 = Ultra, ZS406 / ZS407 = Ultra+).
options:
- tinySA (Basic, ZS304)
- tinySA Ultra (ZS405)
- tinySA Ultra+ (ZS406 / ZS407)
- "No device connected (reproduces without hardware)"
- Other / not sure
validations:
required: true
- type: input
id: firmware
attributes:
label: Firmware build
description: Output of `tsa.version()`, or `version_reply` from the log's first line.
placeholder: "e.g. tinySA4_v1.4-252-g2b4aa51"
validations:
required: false
- type: dropdown
id: mode
attributes:
label: Device mode when it happened
description: Several commands behave differently while the device is in output (signal generator) mode.
options:
- Input mode (spectrum analyzer, the default)
- Output mode (signal generator)
- Switching between the two
- Not sure / not applicable
validations:
required: false
- type: input
id: os
attributes:
Expand All @@ -46,32 +104,14 @@ body:
id: tsapython
attributes:
label: tsapython version
placeholder: "output of: pip show tsapython | grep Version (or 'local build from <commit>')"
placeholder: "output of: python -c \"import tsapython; print(tsapython.__version__)\" (or 'local build from <commit>')"
validations:
required: true
- type: dropdown
id: device
- type: checkboxes
id: other-software
attributes:
label: Device model
label: Anything else holding the serial port?
description: The tinySA has one serial port and one program can own it at a time. `autoconnect()` reports no device when another program has the port open.
options:
- tinySA (Basic)
- tinySA Ultra
- tinySA Ultra+
- "No device connected (reproduces without hardware)"
- Other / not sure
validations:
required: true
- type: textarea
id: firmware
attributes:
label: Device firmware / info
description: Paste the output of `tsa.info()` if a device is involved (redact the serial if you wish).
render: shell
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs / traceback
description: Full traceback or output. Run with `set_verbose(True)` and `set_error_byte_return(True)` if possible.
render: shell
- label: The tinySA web UI, tinySA-App, a terminal program or another script was open at the same time
- label: Nothing else was connected to the device
6 changes: 6 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ contact_links:
- name: tinySA community (groups.io)
url: https://groups.io/g/tinysa
about: Device-usage questions belong here. Note they do NOT support this library — library issues stay on this repo.
- name: tinySA firmware source
url: https://github.com/erikkaashoek/tinySA
about: The device firmware. Bugs in the device itself (not in how this library drives it) belong there.
- name: tinySA web UI
url: https://lc-linkous.github.io/tinysa-web/
about: Browser-based control of the device by the same author. Issues with the web UI go to its own repository, not here.
17 changes: 13 additions & 4 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ body:
out of scope. New device commands are in scope — note that the
`command()` function already provides raw access to any serial command,
so a request for a wrapped method is a request for validation, parsing,
and documentation around it.
documentation and a device-profile gate around it (see "Adding a
command" in CONTRIBUTING.md).

If a **firmware update** added, removed or changed a command, say which
firmware build (`version()`), and attach the output of
`uv run python tests/probe_firmware_returns.py` if you can run it: it
records what your device's firmware accepts, command by command.
- type: textarea
id: problem
attributes:
Expand Down Expand Up @@ -37,6 +43,9 @@ body:
- Display / UI commands
- Presets / config
- System info / connection / autodetect
- Firmware compatibility (a firmware build added, removed or changed a command)
- Device profiles (a new board, or a wrong range / word list for one)
- Logging / error reporting
- Docs / examples
- Other
validations:
Expand All @@ -47,9 +56,9 @@ body:
label: Which device(s) does this apply to?
options:
- All tinySA models
- tinySA (Basic)
- tinySA Ultra
- tinySA Ultra+
- tinySA (Basic, ZS304)
- tinySA Ultra (ZS405)
- tinySA Ultra+ (ZS406 / ZS407)
- Not sure
validations:
required: false
Expand Down
7 changes: 6 additions & 1 deletion .github/ISSUE_TEMPLATE/question.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ body:
[official wiki](https://tinysa.org/wiki/) and the
[tinySA community](https://groups.io/g/tinysa) — they know the hardware
far better, but do not support this library.

Two things worth checking first: the README's command reference (every
wrapped command, with the firmware build it needs where that matters),
and the [tinySA web UI](https://lc-linkous.github.io/tinysa-web/) if you
want to try a command by hand before scripting it.
- type: textarea
id: question
attributes:
Expand All @@ -21,6 +26,6 @@ body:
id: setup
attributes:
label: Setup
placeholder: "e.g. tinySA Ultra, Windows 11, Python 3.12, tsapython 3.0.0"
placeholder: "e.g. tinySA Ultra (ZS405), firmware v1.4-252, Windows 11, Python 3.12, tsapython 4.0.0"
validations:
required: false
23 changes: 15 additions & 8 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@ Closes #

## How it was tested

- [ ] `uv run pytest -m "not hardware"` passes locally (from the `tsapython/` directory)
Run from the `tsapython/` directory:

- [ ] `uv run pytest` passes (hardware tests self-skip without a device)
- [ ] `uv run ruff check .` passes
- [ ] Tested against a **real device** (describe below)
- [ ] Tested with the **mocked fixtures only** (no device)
- [ ] `uv run mypy` passes (new code under `src/tsapython/` is fully annotated)
- [ ] Tested against a **real device**: `uv run pytest -m hardware` — paste the pytest tail below
- [ ] Touches the signal generator: `TSAPYTHON_OUTPUT_OK=1 uv run pytest -m hardware_output` with a load on the RF port
- [ ] Mocked fixtures only (no device) — fine for docs, tests and tooling; core changes need the hardware line above

<!-- If real hardware: which model (Basic / Ultra / Ultra+) and firmware version (from info())? -->
<!-- If real hardware: board (ZS304 / ZS405 / ZS406 / ZS407) and firmware build, from version() / info() -->

## Checklist for new/changed commands

- [ ] Device writes go through `tinySA_serial()` (no direct `self.ser` access from command methods)
- [ ] Added a command-construction test asserting the exact command string via the `recorder` fixture
- [ ] Added a validation-error test asserting **nothing is sent** on bad input
- [ ] Response parsing (if any) is tested with the `fake_port` fixture, ideally against captured device bytes
- [ ] README command reference updated; example added under `examples/` if it's a significant public method
- [ ] Gated on the device profile where the command, an argument word or a range differs by model (`_check_command`, `_check_word`, `self.profile.*`) — see "Adding a command" in CONTRIBUTING.md
- [ ] Any attribute a mixin needs from `core.py` is declared in `_host.py`
- [ ] Command-construction test asserting the exact command string via the `tsa` fixture
- [ ] Validation-error test asserting **nothing is sent** on bad input
- [ ] Response parsing (if any) tested with the `parsing_tsa` fixture, ideally against bytes captured from a device (`tests/collect_samples.py`)
- [ ] README command reference updated (and the "Commands added" table if the command needs a minimum firmware build); example under `examples/` if it's a significant public method
- [ ] CHANGELOG entry under the unreleased version

## Notes for reviewers

Expand Down
21 changes: 21 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Keeps the GitHub Actions and the Python dev dependencies current.
# Runtime dependencies (pyserial, numpy) are deliberately left loose in
# pyproject.toml; Dependabot updates uv.lock, which only affects development
# and CI, and the Actions pins (checkout@v4, setup-uv@v5, ...).
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ["*"]

- package-ecosystem: uv
directory: /tsapython
schedule:
interval: monthly
groups:
dev-dependencies:
patterns: ["*"]
32 changes: 24 additions & 8 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: Build release assets
# Two ways to run it:
# 1. Push a tag `vX.Y.Z`. A DRAFT release is created for the tag with the
# built files attached. Edit the notes on the release page, then publish.
# Publishing fires release.yml, which rebuilds and uploads to PyPI.
# Publishing fires release.yml, which uploads THESE files to PyPI.
# 2. Actions tab -> "Build release assets" -> Run workflow, and type the tag.
# Use this to (re)attach files to a release that already exists.
#
Expand All @@ -25,7 +25,7 @@ on:
type: string

permissions:
contents: write # create the draft release and upload its assets
contents: read # only the 'attach' job below gets write

defaults:
run:
Expand Down Expand Up @@ -57,7 +57,7 @@ jobs:
python-version: "3.12"

- name: Sync environment
run: uv sync
run: uv sync --locked

- name: Test suite, lint, type check
run: |
Expand Down Expand Up @@ -89,18 +89,34 @@ jobs:
name: dist-${{ env.TAG }}
path: tsapython/dist/

attach:
# Separate job so the write token is never in scope while tests and
# third-party actions run above.
needs: build
runs-on: ubuntu-latest
permissions:
contents: write # create the draft release and upload its assets
env:
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
GH_TOKEN: ${{ github.token }}
defaults:
run:
working-directory: .
steps:
- name: Fetch the built files
uses: actions/download-artifact@v4
with:
name: dist-${{ env.TAG }}
path: dist/

- name: Create the draft release if the tag has none
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
echo "release $TAG exists; attaching files to it"
else
notes="Draft created by the build-release workflow. Paste the release notes here, then publish; publishing runs release.yml and uploads to PyPI."
notes="Draft created by the build-release workflow. Paste the release notes here, then publish; publishing runs release.yml, which uploads these files to PyPI."
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" --draft --title "$TAG" --notes "$notes"
fi

- name: Attach files to the release
env:
GH_TOKEN: ${{ github.token }}
run: gh release upload "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --clobber
26 changes: 26 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CodeQL

# Static analysis for the Python source. Runs on pushes and PRs to main and
# once a week, so a new query that matches old code is still reported.
on:
push:
branches: [main, "v*"]
pull_request:
branches: [main]
schedule:
- cron: "17 6 * * 1"

permissions:
contents: read
security-events: write

jobs:
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with:
languages: python
queries: security-and-quality
- uses: github/codeql-action/analyze@v3
Loading
Loading