Skip to content
RFrftools.io
ToolsMarch 11, 20266 min read

Automate RF Calculations in Python with rftools

The rftools Python package gives you programmatic access to 203 RF and electronics calculators from rftools.io — with a typed API, CLI, batch mode, and async.

Contents

Why Automate RF Calculations?

Look, doing one VSWR calculation by hand is no big deal. But when you're sweeping 50 different cable lengths, comparing link budgets for a dozen antenna options, or putting together a Jupyter notebook for your next design review, clicking through web forms gets old fast.

The rftools Python package puts the entire rftools.io calculator engine right in your Python environment. No web scraping, no copying numbers between windows — just straightforward function calls with proper types. I've seen too many spreadsheets with broken formulas and copy-paste errors. This is cleaner.

Installation

pip install rftools-io

You'll need a free API key from rftools.io/pricing — the free tier gives you 5 calls per month, which is fine for kicking the tires. If you're building this into an actual workflow or running sweeps regularly, the API tier bumps you to 10,000 calls/month. That's enough for most real work.

Your First Calculation

import rftools

result = rftools.calculate('vswr-return-loss', {'vswr': 2.5})
print(f'Return Loss: {result["returnLoss"]:.2f} dB')  # 9.54 dB
print(f'Reflection Coeff: {result["reflectionCoeff"]:.3f}')  # 0.333

The rftools.calculate() function returns a CalculatorResult object. It behaves like a dictionary, so you can pull out results by key. Simple enough — you pass in the calculator slug and a dict of inputs, and you get your answers back.

Typed Stubs for IDE Autocomplete

If you want better discoverability and proper IDE autocomplete (and you should), use the typed category modules instead of the generic calculate() function:

from rftools.calculators import rf, antenna, pcb

# Parameter names and defaults match what you see on the rftools.io web UI
fspl = rf.free_space_path_loss(frequency=2400.0, distance=100.0)
print(f'FSPL: {fspl["pathLoss"]:.1f} dB')  # 80.0 dB

dipole = antenna.dipole_antenna(frequency=433.0)
print(f'Dipole length: {dipole["length"]:.0f} mm')

All 13 categories are available: rf, pcb, power, signal, antenna, general, motor, protocol, emc, thermal, sensor, unit_conversion, and audio. Your IDE will show you the function signatures, which beats digging through documentation every time you forget whether it's txPower or tx_power.

Batch Mode (API Tier)

Here's where things get useful for real work. The batch API lets you run up to 50 calculations in a single HTTP request. This is perfect for parameter sweeps where you'd otherwise be making dozens of individual API calls:

import rftools

client = rftools.Client(api_key='rfc_live_xxx')
# or just: export RFTOOLS_API_KEY=rfc_live_xxx

distances = [10, 50, 100, 500, 1000]
results = client.batch([
    ('free-space-path-loss', {'frequency': 2400, 'distance': d})
    for d in distances
])

for d, r in zip(distances, results):
    if r.ok:
        print(f'{d:>6}m  →  {r.values["pathLoss"]:.1f} dB')

Output:

    10m  →  60.0 dB
    50m  →  74.0 dB
   100m  →  80.0 dB
   500m  →  94.0 dB
  1000m  →  100.0 dB

This is way faster than looping through individual calls, and it doesn't hammer the API with a flood of requests. The batch endpoint is designed for exactly this kind of thing.

Async Support

If you're building a FastAPI service or working in an async Jupyter kernel, there's an AsyncClient that plays nicely with Python's async/await pattern:

import asyncio
import rftools

async def main():
    async with rftools.AsyncClient(api_key='rfc_live_xxx') as client:
        result = await client.calculate('rf-link-budget', {
            'txPower': 20,
            'txGain': 6,
            'rxGain': 3,
            'frequency': 2400,
            'distance': 500,
        })
        print(f'Received power: {result["rxPower"]:.1f} dBm')

asyncio.run(main())

The async client uses the same API under the hood, just with non-blocking I/O. If you're already in an async codebase, this keeps everything consistent.

CLI

Sometimes you just need a quick answer in the terminal. The rftools command-line tool handles that:

# Single calculation
rftools calc vswr-return-loss --vswr 2.5

# JSON output — pipe to jq or whatever
rftools calc vswr-return-loss --vswr 2.5 --json | jq '.values.returnLoss'

# List available calculators in a category
rftools list --category rf

# Show what inputs and outputs a calculator expects
rftools info free-space-path-loss

I use this mostly for sanity checks when I'm already at the command line and don't want to fire up Python. It's also handy in shell scripts if you're automating something quick and dirty.

Error Handling

The library raises typed exceptions, so you can catch specific problems instead of generic errors:

from rftools.exceptions import AuthError, RateLimitError, ValidationError

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 ValidationError as e:
    print(f'Bad inputs: {e.detail}')
RateLimitError even tells you how long to wait before retrying, which is useful if you're building retry logic into a production system. The ValidationError will tell you exactly which input was wrong, so you're not stuck guessing.

Browsing the Calculator Catalog

There are 203 calculators available. You can list them programmatically if you need to build tooling on top of this:

# 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}')

# Inspect a specific calculator's inputs and outputs
info = rftools.get_calculator('noise-figure-cascade')
for field in info.inputs:
    print(f'  in:  {field.id} ({field.unit})')
for field in info.outputs:
    print(f'  out: {field.id} ({field.unit})')

This is particularly useful if you're building a UI or automation layer that needs to discover what calculators are available and what parameters they accept. The metadata includes units, default values, and descriptions for each field.

Here's a real-world example: plotting received power versus distance for a 915 MHz link. This is the kind of thing you'd do in a Jupyter notebook when you're sizing up whether a link will close at your maximum range.

import numpy as np
import matplotlib.pyplot as plt
from rftools.calculators import rf

distances = np.logspace(1, 4, 40)  # 10m to 10km
rx_powers = []

for d in distances:
    r = rf.rf_link_budget(
        txPower=30,     # dBm
        txGain=6,       # dBi
        rxGain=6,       # dBi
        frequency=915,  # MHz
        distance=float(d),
    )
    rx_powers.append(r['rxPower'])

plt.semilogx(distances, rx_powers)
plt.axhline(-100, color='r', linestyle='--', label='Sensitivity (-100 dBm)')
plt.xlabel('Distance (m)')
plt.ylabel('Received Power (dBm)')
plt.title('915 MHz Link Budget')
plt.legend()
plt.grid(True)
plt.show()

This gives you a nice logarithmic plot showing where your link drops below receiver sensitivity. You can tweak transmit power, antenna gains, or frequency and immediately see the impact. Way faster than recalculating everything by hand or clicking through a web form 40 times.

You could extend this to include fade margin, compare different antenna configurations, or overlay measured data from field tests. The point is you've got the full calculator engine available in a scriptable environment, so you can build whatever analysis tools you actually need.

Getting Started

Install it with pip install rftools-io. The source code and issue tracker are at github.com/rftools/rftools-py. Head over to rftools.io/pricing to grab an API key and see the pricing tiers. The free tier is fine for trying things out, but if you're doing serious work, the paid tier is cheap enough that you won't think twice about it.

Related Articles