Metadata-Version: 2.4
Name: hubble-satnet-decoder
Version: 1.1.1
Summary: Hubble PHY v-1 / v1 preamble detector, FSK decoder, and spectrogram computation
Author: Hubble Network
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: numpy<2,>=1.26
Requires-Dist: opencv-python-headless
Requires-Dist: reedsolo
Requires-Dist: scipy>=1.13
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# hubble-satnet-decoder

[![CI](https://github.com/hubblenetwork/hubble-satnet-decoder/actions/workflows/ci.yml/badge.svg)](https://github.com/hubblenetwork/hubble-satnet-decoder/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

Hubble PHY v-1 / v1 preamble detector, FSK decoder, and spectrogram
computation — extracted as a standalone, pip-installable library.

## Install

```bash
pip install hubble-satnet-decoder
```

Or for development:

```bash
git clone https://github.com/hubblenetwork/hubble-satnet-decoder.git
cd hubble-satnet-decoder
pip install -e ".[dev]"
```

## Quick start

```python
import numpy as np
from hubble_satnet_decoder import decode_signal, configure

# (optional) override the default sample rate
configure(sample_rate=781_250)

# Load 1 second of IQ data (complex64)
iq = np.load("capture.npy")

packets, detections, attempts = decode_signal(iq)
for pkt in packets:
    print(f"Device 0x{pkt['ntw_id']:08X}  seq={pkt['seq_num']}  "
          f"chipset={pkt.get('chipset', 'v-1')}")
```

## API reference

### `decode_signal(signal) -> (packets, detections, attempts)`

Full dual-protocol decode pipeline on a 1-second IQ chunk.

### `detect_preambles(spec_img, t_det, f_det) -> (time, freq, scores, phy_ver)`

Dual-template preamble detection via OpenCV + NMS.

### `compute_spec_chunk(iq_chunk) -> Sxx_dB`

Compute a visualisation spectrogram (freq × time) for an IQ chunk.

### `configure(sample_rate=None)`

Recompute all sample-rate-dependent derived values.

### Constants

```python
from hubble_satnet_decoder import (
    SYNTH_RES,             # per-chipset synthesiser resolution (Hz)
    CHANNEL_SPACING,       # nominal channel spacing (Hz)
    DEVICE_CHANNEL_SPACING,# actual per-device channel spacing
    HOPPING_SEQS,          # frequency hopping sequences
    RS_N_V1, RS_K_V1,      # Reed-Solomon block sizes (v1)
    PREAMBLE_CODE_V1,      # v1 preamble code [63,0,63,0,63,0,63,63]
)
```

### Chipset statistics

```python
from hubble_satnet_decoder import get_chipset_stats, reset_chipset_stats

stats = get_chipset_stats()   # {"nordic": {"detected": 5, "ok": 4, ...}, ...}
reset_chipset_stats()
```

## Releasing

1. **Bump the version** in `pyproject.toml` (e.g. `version = "1.0.2"`)
2. **Write release notes** in `release-notes.md` — categorise commits since the last tag under `Added`, `Fixed`, `Documentation`, `Tests`, and `Maintenance` headings
3. **Commit**:
   ```bash
   git add pyproject.toml release-notes.md
   git commit -m "chore: release X.Y.Z"
   ```
4. **Tag and push**:
   ```bash
   git tag vX.Y.Z
   git push origin main
   git push origin vX.Y.Z
   ```
5. **Approve the publish** — the tag push triggers the [GitHub Actions workflow](.github/workflows/release.yml) which runs tests, builds the package, and publishes to PyPI. Approve the publish step in the GitHub Actions UI (the `pypi` environment gate).

> **Tip:** The `/release` Claude Code skill automates steps 1–4.

## License

Apache-2.0 — see [LICENSE](LICENSE).
