Metadata-Version: 2.4
Name: betaflight-chirp-core
Version: 0.1.7
Summary: Betaflight closed-loop chirp / blackbox analysis core — decoder, FRF/Bode, step response, spectral, HTML report.
Author-email: Sebastien Galina <galina.sebastien@gmail.com>
License:                                  Apache License
                                   Version 2.0, January 2004
                                http://www.apache.org/licenses/
        
           Licensed under the Apache License, Version 2.0 (the "License");
           you may not use this file except in compliance with the License.
           You may obtain a copy of the License at
        
               http://www.apache.org/licenses/LICENSE-2.0
        
           Unless required by applicable law or agreed to in writing, software
           distributed under the License is distributed on an "AS IS" BASIS,
           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           See the License for the specific language governing permissions and
           limitations under the License.
        
           Copyright 2026 betaflight-chirp-core contributors
        
Project-URL: Homepage, https://github.com/SebGalina/betaflight-chirp-core
Project-URL: Repository, https://github.com/SebGalina/betaflight-chirp-core
Project-URL: Issues, https://github.com/SebGalina/betaflight-chirp-core/issues
Project-URL: Changelog, https://github.com/SebGalina/betaflight-chirp-core/blob/main/CHANGELOG.md
Keywords: betaflight,drone,fpv,blackbox,chirp,frf,bode,pid,tuning
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24.0
Requires-Dist: pandas>=2.0.0
Requires-Dist: scipy>=1.10.0
Provides-Extra: test
Requires-Dist: pytest; extra == "test"
Provides-Extra: plot
Requires-Dist: matplotlib; extra == "plot"
Dynamic: license-file

# betaflight-chirp-core

[![PyPI](https://img.shields.io/pypi/v/betaflight-chirp-core)](https://pypi.org/project/betaflight-chirp-core/)
[![Python](https://img.shields.io/pypi/pyversions/betaflight-chirp-core)](https://pypi.org/project/betaflight-chirp-core/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)

The compute core for Betaflight closed-loop **chirp** / blackbox analysis:
decode a `.bbl`/`.bfl`, estimate the frequency response (FRF/Bode), step
response and noise spectrum, and render a self-contained HTML report.

> `betaflight-chirp-core` knows nothing about MCP, HTTP, Docker, the CLI or the
> filesystem. **Input: bytes. Output: objects + HTML.**

## Install

```bash
pip install betaflight-chirp-core
```

Or pin an exact commit straight from git:

```bash
pip install "betaflight-chirp-core @ git+https://github.com/SebGalina/betaflight-chirp-core@v0.1.5"
```

## Usage

```python
from betaflight_chirp_core import decode, analyse_log, build_report, run

# low-level: decode -> analyse -> render, step by step
df, fs, config = decode(open("log.bbl", "rb").read())
# df: decoded frames (pandas)   fs: loop/log rate (Hz)   config: PID/filter settings
a_pass = analyse_log(df, fs, config)        # one log  -> one self-contained pass dict
html   = build_report([a_pass])             # passes   -> self-contained HTML report

# single call: decode + analyse + report in one shot
result = run(open("log.bbl", "rb").read())
result.metrics       # == result.raw["axes"] — per-axis indicators, render as-is
result.report_html   # the self-contained HTML report (a full <html> string)
result.raw           # the complete pass dict (see Output below)
```

Importing the package is **light**: numpy/scipy/pandas load lazily, only when an
analysis runs. `from betaflight_chirp_core import decoder` stays stdlib-only, so
decode-only callers pull no heavy deps.

## Output

Four return surfaces, from raw to ready-to-render:

| Call | Returns | Is |
|---|---|---|
| `decode(bytes)`        | `(df, fs, config)`           | pandas frames, log rate (Hz), header tune dict |
| `analyse_log(df,…)`    | **pass dict**                | one log → all indicators (below) |
| `build_report(passes)` | `str`                        | one self-contained `<html>` page |
| `run(bytes)`           | `AnalysisResult`             | `.metrics` (= `raw["axes"]`), `.report_html`, `.raw` (pass dict) |

### The pass dict

`analyse_log()` / `result.raw` — one self-contained analysis of one log:

```python
{
  "timestamp":   "2026-06-12T09:00:00",   # ISO, when analysed
  "file":        "log.bbl",
  "sample_rate_hz": 8000,                  # loop/log rate
  "input_col":   "debug[3]",               # FRF input column (chirp setpoint channel)
  "band_hz":     [1.0, 1000.0],            # analysed frequency band [fmin, fmax]
  "throttle_max": 1850,                    # peak flying throttle (or None)
  "config":      {…},                      # PID / filter settings parsed from the header
  "axes":        {"roll": {…}, "pitch": {…}, "yaw": {…}},   # per-axis, see below
  "tune_score":  {"overall": 76.0, "grade": "B", "axes": {"roll": {"score": …, "subs": {…}}}},
  "throttle_map":   {…},                   # resonance vs throttle (heatmap payload)
  "noise_spectrum": {…},                   # gyro PSD raw vs filtered (see below)
  "spectrogram":    {…},                   # chirp sweep time×freq (heatmap payload)
  "synthesis":      [{"fr": "...", "en": "..."}, …],   # plain-language read, bilingual
  "filter_suggestions": [ … ],             # filter change hints (only when config present)
  "noise_suggestions":  [ … ],             # noise/peak hints
}
```

**Per axis** (`axes["roll"]` etc.) — the Bode + step + verdict for one axis:

```python
{
  "band_hz": [1.0, 1000.0], "n_samples": 48000,
  "freq": [...], "gain_db": [...], "phase_deg": [...], "coherence": [...],  # Bode curves
  "peaks": [ … ],                          # gain-resonance peaks in band
  "crossover_hz": 32.0,                    # 0 dB crossover
  "phase_margin_deg": 41.0, "phase_margin_unc_deg": 6.0,
  "ms": 4.8, "f_ms_hz": 70.0, "pm_guaranteed_deg": 34.0,   # peak sensitivity (robustness)
  "step": {                                # setpoint→gyro step response
    "t_ms": [...], "y": [...], "y_lo": [...], "y_hi": [...],
    "metrics": {"overshoot_pct": 12.0, "rise_ms": 18.0, "delay_ms": 3.0,
                "settle_ms": 60.0, "peak": 1.12},
  },
  "diagnosis": [ … ], "step_diagnosis": [ … ],   # short verdict strings
}
```

`tune_score.grade` is an **A–F** letter (`A` ≥ 85 … `F` < 40); `overall` is the mean
of the per-axis scores. `noise_spectrum` carries `freqs`, `raw_db`/`filt_db` curves
(0 dB = raw broadband floor) and a `peaks` list with `above_floor_db` / `resid_db`
(filtered residual) / `atten_db` (raw→filtered cut) per peak.

> Array fields (`freq`, `gain_db`, `*_db`, …) are JSON-ready (rounded floats), so the
> whole pass dict serialises straight to a front-end or a history store. For the exact
> nested fields, read `analysis/chirp.py:build_pass`.

## Layout

| Module | Role |
|---|---|
| `decoder.py`      | pure-Python `.bbl` frame decoder (stdlib only) |
| `signal.py`       | `decode_dataframe` (bytes → frames), `sample_rate`, `active_mask` |
| `config.py`       | PID / filter settings parsed from the header |
| `analysis/`       | chirp (FRF/Bode), spectral, step response |
| `report.py`       | self-contained HTML report (inlines the renderer assets) |
| `report_assets/`  | shared report renderer (`chirp_report.{js,css}` + glossary/strings JSON) — inlined by `report.py`, mountable by a web front |

## Develop

```bash
pip install -e ".[test]"
pytest
```

Tests run on `.bbl` fixtures in `tests/data/`. One GPS-free log (`8.bbl`) ships so
the suite runs out of the box; drop your own logs there for more coverage. Every
other `.bbl`/`.bfl` is git-ignored — **never commit a real flight log**, it
carries GPS home-point coordinates (only `8.bbl` is whitelisted, after verifying
it has no GPS frame).

## License

Apache-2.0.
