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.
A Practical Example: Link Budget Sweep
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
Sizing a 9600-baud UHF Downlink for a 3U CubeSat: Full Walkthrough
End-to-end link budget for an amateur-band 3U cubesat: EIRP, ground-station G/T, ITU-R propagation losses, and Monte Carlo availability. Uses the Amateur CubeSat preset.
Apr 29, 2026
Satellite CommunicationsMigrating from STK Cloud: Free Alternatives for Link Budget and Orbit Analysis
Ansys is sunsetting STK Cloud in March 2026. Here are the free open-source replacements for the two things it did best — ITU-R link budgets and orbital pass prediction.
Apr 29, 2026
RF EngineeringRF Power Density: Calculating EM Exposure
Master RF power density calculations with real-world examples and critical engineering insights for wireless and electromagnetic safety analysis.
Apr 29, 2026