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
Angle Conversions in RF Electronics: Why Units Matter
Convert angles between degrees, radians, gradians, arcminutes, and arcseconds. Essential for antenna pointing, phase calculations, and servo control.
Jul 20, 2026
Antenna DesignAntenna Beamwidth: The Math Behind Signal Pointing
Calculate antenna beamwidth and gain from aperture dimensions. Covers 3dB beamwidth, first null, and real examples for dish and horn antennas.
Jul 20, 2026
Audio Engineering24-Bit ADC Dynamic Range: Why You Don't Get 144 dB
Calculate real ADC dynamic range from bit depth and oversampling. Covers SNR theory, sigma-delta gains, and why datasheet specs mislead.
Jul 20, 2026