# rftools

[![PyPI](https://img.shields.io/pypi/v/rftools)](https://pypi.org/project/rftools/)
[![Python](https://img.shields.io/pypi/pyversions/rftools)](https://pypi.org/project/rftools/)

Python client for **rftools.io** — 203 RF & electronics calculators accessible from Python and the command line.

## Installation

```bash
pip install rftools-io
```

## Quick Start

Get a free API key at [rftools.io/pricing](https://rftools.io/pricing) (5 calls/month free).

```bash
export RFTOOLS_API_KEY=rfc_your_key_here
```

```python
import rftools

result = rftools.calculate("vswr-return-loss", {"vswr": 2.5})
print(result["returnLoss"])  # 9.54 dB
print(result.values)         # {"returnLoss": 9.54, "reflectionCoeff": 0.333, ...}
```

## Authenticated Usage (API tier)

Get an API key at [rftools.io/pricing](https://rftools.io/pricing) ($19/mo, 10,000 calls/month).

```python
import rftools

client = rftools.Client(api_key="rfc_live_xxx")
# or set environment variable: export RFTOOLS_API_KEY=rfc_live_xxx

result = client.calculate("free-space-path-loss", {"frequency": 2400, "distance": 100})
print(result["pathLoss"])  # dB
```

## Batch Calculations

Run up to 50 calculations in a single HTTP request (API tier only):

```python
results = client.batch([
    ("vswr-return-loss", {"vswr": 1.5}),
    ("vswr-return-loss", {"vswr": 2.0}),
    ("free-space-path-loss", {"frequency": 2400, "distance": 50}),
])
for r in results:
    if r.ok:
        print(r.values)
    else:
        print(f"Error: {r.error}")
```

## Typed Category Stubs

IDE-friendly typed functions with parameter defaults matching the web UI:

```python
from rftools.calculators import rf, pcb, antenna, power

# RF
result = rf.vswr_return_loss(vswr=2.5)
result = rf.free_space_path_loss(frequency=2400.0, distance=100.0)

# Antenna
result = antenna.dipole_antenna(frequency=433.0)
result = antenna.parabolic_dish_antenna(frequency=10000.0, diameter=0.6)

# PCB
result = pcb.trace_width_current(current=2.0, tempRise=10.0, thickness=1.0)

# Power
result = power.voltage_divider(vin=12.0, r1=10000.0, r2=10000.0)
```

All 13 categories available: `rf`, `pcb`, `power`, `signal`, `antenna`, `general`, `motor`, `protocol`, `emc`, `thermal`, `sensor`, `unit_conversion`, `audio`.

## CLI

```bash
# Run a calculation
rftools calc vswr-return-loss --vswr 2.5

# With API key
RFTOOLS_API_KEY=rfc_xxx rftools calc free-space-path-loss --frequency 2400 --distance 100

# JSON output (pipe to jq)
rftools calc vswr-return-loss --vswr 2.5 --json | jq '.values.returnLoss'

# List all calculators
rftools list

# Filter by category
rftools list --category rf

# Show calculator inputs/outputs
rftools info free-space-path-loss

# Library version
rftools version
```

## Error Handling

```python
from rftools.exceptions import AuthError, RateLimitError, ValidationError, NotFoundError, APIError

try:
    result = client.calculate("vswr-return-loss", {"vswr": 2.5})
except RateLimitError as e:
    print(f"Quota exceeded. Retry after: {e.retry_after}s")
except AuthError:
    print("Invalid API key")
except NotFoundError:
    print("Unknown calculator slug")
except ValidationError as e:
    print(f"Bad inputs: {e.detail}")
```

| Exception | When |
|---|---|
| `AuthError` | Invalid or missing API key |
| `RateLimitError` | Monthly quota exceeded |
| `ValidationError` | Bad inputs (HTTP 422) |
| `NotFoundError` | Unknown calculator slug |
| `APIError` | Unexpected HTTP error |

## Async Support

```python
import asyncio
import rftools

async def main():
    async with rftools.AsyncClient(api_key="rfc_live_xxx") as client:
        result = await client.calculate("vswr-return-loss", {"vswr": 2.5})
        print(result["returnLoss"])

asyncio.run(main())
```

Ideal for running many calculations concurrently in FastAPI or async scripts.

## Browse the Catalog

```python
# List all calculators
calcs = rftools.list_calculators()
print(f"{len(calcs)} calculators available")

# Filter by category
rf_calcs = rftools.list_calculators(category="rf")
for c in rf_calcs:
    print(f"{c.slug}: {c.title}")

# Get a single calculator's metadata
info = rftools.get_calculator("vswr-return-loss")
print(info.inputs)   # tuple of InputField
print(info.outputs)  # tuple of OutputField
```

## All Calculator Categories

| Category | Count | Example |
|---|---|---|
| `rf` | 26 | `vswr-return-loss`, `free-space-path-loss`, `rf-link-budget` |
| `pcb` | 13 | `trace-width-current`, `via-calculator`, `microstrip-impedance` |
| `power` | 20 | `voltage-divider`, `led-resistor`, `battery-life` |
| `signal` | 13 | `filter-designer`, `op-amp-gain`, `pwm-duty-cycle` |
| `antenna` | 8 | `dipole-antenna`, `eirp-calculator`, `parabolic-dish-antenna` |
| `general` | 21 | `lc-resonance`, `ohms-law`, `rc-time-constant` |
| `motor` | 18 | `brushless-dc-motor`, `stepper-motor`, `servo-motor` |
| `protocol` | 11 | `uart-baud-rate`, `i2c-pullup`, `can-bus-bit-timing` |
| `emc` | 16 | `emi-filter-lc`, `shielding-effectiveness`, `ground-loop` |
| `thermal` | 6 | `thermal-resistance`, `heat-sink`, `junction-temperature` |
| `sensor` | 17 | `thermistor-ntc`, `strain-gauge`, `hall-effect` |
| `unit-conversion` | 17 | `dbm-watts`, `frequency-wavelength`, `temperature` |
| `audio` | 17 | `speaker-crossover`, `amplifier-gain`, `room-acoustics` |

## Contributing

Regenerate stubs after calculator changes:

```bash
python scripts/generate_stubs.py --frontend-dir /path/to/rfhub/frontend
```

Then bump the version in `pyproject.toml` and publish a new release.

## License

MIT — see [LICENSE](LICENSE).
