viewof loraTxPower = Inputs.range([2, 20], {
value: 14,
step: 1,
label: "Transmit Power (dBm):",
width: 300
})
viewof loraAntennaTx = Inputs.range([0, 6], {
value: 2.15,
step: 0.5,
label: "TX Antenna Gain (dBi):",
width: 300
})
viewof loraAntennaRx = Inputs.range([0, 6], {
value: 2.15,
step: 0.5,
label: "RX Antenna Gain (dBi):",
width: 300
})
viewof loraFrequency = Inputs.select(
[
{label: "868 MHz (EU)", value: 868},
{label: "915 MHz (US)", value: 915}
],
{
label: "Frequency Band:",
format: x => x.label,
value: {label: "868 MHz (EU)", value: 868}
}
)
viewof loraSF = Inputs.select(
[
{label: "SF7 (Fastest)", value: 7},
{label: "SF8", value: 8},
{label: "SF9", value: 9},
{label: "SF10", value: 10},
{label: "SF11", value: 11},
{label: "SF12 (Longest Range)", value: 12}
],
{
label: "Spreading Factor:",
format: x => x.label,
value: {label: "SF10", value: 10}
}
)
viewof loraBW = Inputs.select(
[
{label: "125 kHz (Standard)", value: 125},
{label: "250 kHz", value: 250},
{label: "500 kHz (Fastest)", value: 500}
],
{
label: "Bandwidth:",
format: x => x.label,
value: {label: "125 kHz (Standard)", value: 125}
}
)
// LoRa Link Budget Calculations
loraLinkBudget = {
const sf = loraSF.value;
const bw = loraBW.value;
const freq = loraFrequency.value;
const txPower = loraTxPower;
const antTx = loraAntennaTx;
const antRx = loraAntennaRx;
// Receiver sensitivity formula for LoRa
// Sensitivity = -174 + 10*log10(BW) + NF + SNR_required
// NF (Noise Figure) typically 6 dB for SX127x
// SNR required depends on SF
const snrRequired = {
7: -7.5,
8: -10,
9: -12.5,
10: -15,
11: -17.5,
12: -20
};
const noiseFigure = 6; // dB, typical for SX1276
const thermalNoise = -174; // dBm/Hz at room temperature
const sensitivity = thermalNoise + 10 * Math.log10(bw * 1000) + noiseFigure + snrRequired[sf];
// Data rate calculation
// DR = SF * (BW / 2^SF) * CR
// Assuming CR = 4/5 = 0.8
const codingRate = 0.8;
const dataRate = sf * (bw * 1000 / Math.pow(2, sf)) * codingRate;
// Time on air for 12-byte payload (typical sensor reading)
// Simplified formula: ToA = (payload_symbols + preamble) * symbol_time
const symbolTime = Math.pow(2, sf) / (bw * 1000);
const preambleSymbols = 8 + 4.25; // Default LoRa preamble
const payloadBytes = 12;
const payloadSymbols = Math.ceil((8 * payloadBytes - 4 * sf + 44) / (4 * sf)) * (1 / codingRate);
const timeOnAir = (preambleSymbols + payloadSymbols) * symbolTime * 1000; // ms
// Link budget
const eirp = txPower + antTx; // Effective Isotropic Radiated Power
const linkBudget = eirp + antRx - sensitivity;
// Free Space Path Loss at 1 km
// FSPL = 20*log10(d) + 20*log10(f) + 32.44
const fspl1km = 20 * Math.log10(1) + 20 * Math.log10(freq) + 32.44;
// Maximum range calculation for different environments
// Using path loss exponent model: PL = FSPL(1km) + 10*n*log10(d)
// Solving for d: d = 10^((linkBudget - FSPL(1km)) / (10*n))
const pathLossExponents = {
urban: 3.5,
suburban: 3.0,
rural: 2.5
};
const maxRange = {};
for (const [env, n] of Object.entries(pathLossExponents)) {
const exponent = (linkBudget - fspl1km) / (10 * n);
maxRange[env] = Math.pow(10, exponent);
}
// Link margin at typical deployment distances
const typicalDistances = [1, 2, 5, 10, 15];
const linkMargins = typicalDistances.map(d => {
const pl = fspl1km + 10 * 3.0 * Math.log10(d); // Suburban path loss
return {
distance: d,
pathLoss: pl,
margin: linkBudget - pl
};
});
// Sensitivity table for all SFs at current bandwidth
const sensitivityTable = [];
for (let s = 7; s <= 12; s++) {
const sens = thermalNoise + 10 * Math.log10(bw * 1000) + noiseFigure + snrRequired[s];
const dr = s * (bw * 1000 / Math.pow(2, s)) * codingRate;
const lb = eirp + antRx - sens;
const rangeSuburban = Math.pow(10, (lb - fspl1km) / (10 * 3.0));
sensitivityTable.push({
sf: s,
sensitivity: sens.toFixed(1),
dataRate: dr.toFixed(0),
linkBudget: lb.toFixed(1),
maxRange: rangeSuburban.toFixed(1),
selected: s === sf
});
}
return {
sensitivity: sensitivity.toFixed(1),
dataRate: dataRate.toFixed(0),
dataRateKbps: (dataRate / 1000).toFixed(2),
timeOnAir: timeOnAir.toFixed(1),
linkBudget: linkBudget.toFixed(1),
eirp: eirp.toFixed(1),
fspl1km: fspl1km.toFixed(1),
maxRange,
linkMargins,
sensitivityTable,
sf,
bw,
freq
};
}
md`### Link Budget Results
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0;">
<div style="background: linear-gradient(135deg, #2C3E50, #34495E); color: white; padding: 20px; border-radius: 10px; text-align: center;">
<div style="font-size: 0.9em; opacity: 0.8;">Receiver Sensitivity</div>
<div style="font-size: 2em; font-weight: bold;">${loraLinkBudget.sensitivity} dBm</div>
<div style="font-size: 0.8em; opacity: 0.7;">SF${loraLinkBudget.sf} @ ${loraLinkBudget.bw} kHz</div>
</div>
<div style="background: linear-gradient(135deg, #16A085, #1ABC9C); color: white; padding: 20px; border-radius: 10px; text-align: center;">
<div style="font-size: 0.9em; opacity: 0.8;">Link Budget</div>
<div style="font-size: 2em; font-weight: bold;">${loraLinkBudget.linkBudget} dB</div>
<div style="font-size: 0.8em; opacity: 0.7;">EIRP: ${loraLinkBudget.eirp} dBm</div>
</div>
<div style="background: linear-gradient(135deg, #E67E22, #F39C12); color: white; padding: 20px; border-radius: 10px; text-align: center;">
<div style="font-size: 0.9em; opacity: 0.8;">Data Rate</div>
<div style="font-size: 2em; font-weight: bold;">${loraLinkBudget.dataRateKbps} kbps</div>
<div style="font-size: 0.8em; opacity: 0.7;">${loraLinkBudget.dataRate} bps</div>
</div>
<div style="background: linear-gradient(135deg, #9B59B6, #8E44AD); color: white; padding: 20px; border-radius: 10px; text-align: center;">
<div style="font-size: 0.9em; opacity: 0.8;">Time on Air (12 bytes)</div>
<div style="font-size: 2em; font-weight: bold;">${loraLinkBudget.timeOnAir} ms</div>
<div style="font-size: 0.8em; opacity: 0.7;">Airtime per packet</div>
</div>
</div>
### Maximum Range Estimates
| Environment | Path Loss Exponent | Maximum Range | Notes |
|-------------|-------------------|---------------|-------|
| **Urban** (Dense buildings) | n = 3.5 | **${loraLinkBudget.maxRange.urban.toFixed(2)} km** | Heavy multipath, NLOS |
| **Suburban** (Mixed area) | n = 3.0 | **${loraLinkBudget.maxRange.suburban.toFixed(2)} km** | Moderate obstacles |
| **Rural** (Open terrain) | n = 2.5 | **${loraLinkBudget.maxRange.rural.toFixed(2)} km** | Near line-of-sight |
### Link Margin at Distance (Suburban Environment)
| Distance | Path Loss | Link Margin | Status |
|----------|-----------|-------------|--------|
${loraLinkBudget.linkMargins.map(m => `| ${m.distance} km | ${m.pathLoss.toFixed(1)} dB | ${m.margin.toFixed(1)} dB | ${m.margin > 10 ? "Excellent" : m.margin > 5 ? "Good" : m.margin > 0 ? "Marginal" : "No Link"} |`).join('\n')}
*Link margin > 10 dB recommended for reliable operation. Margin > 0 dB required for any link.*
### Sensitivity Table: All Spreading Factors @ ${loraLinkBudget.bw} kHz
| SF | Sensitivity | Data Rate | Link Budget | Max Range (Suburban) | Trade-off |
|----|-------------|-----------|-------------|---------------------|-----------|
${loraLinkBudget.sensitivityTable.map(row =>
`| ${row.selected ? "**SF" + row.sf + "**" : "SF" + row.sf} | ${row.sensitivity} dBm | ${row.dataRate} bps | ${row.linkBudget} dB | ${row.maxRange} km | ${row.sf === 7 ? "Fastest, shortest" : row.sf === 12 ? "Slowest, longest" : "Balanced"} |`
).join('\n')}
### Data Rate vs Range Trade-off
The fundamental LoRa trade-off: **Higher SF = More range but less data rate**
- **SF7**: Best for short-range, high-throughput (${loraLinkBudget.sensitivityTable[0].dataRate} bps)
- **SF12**: Best for maximum range at cost of speed (${loraLinkBudget.sensitivityTable[5].dataRate} bps)
- **Ratio**: SF12 provides ~${(parseFloat(loraLinkBudget.sensitivityTable[5].maxRange) / parseFloat(loraLinkBudget.sensitivityTable[0].maxRange)).toFixed(1)}x more range than SF7, but ${(parseFloat(loraLinkBudget.sensitivityTable[0].dataRate) / parseFloat(loraLinkBudget.sensitivityTable[5].dataRate)).toFixed(0)}x slower
**Battery Impact**: Each SF increase roughly doubles airtime, doubling energy consumption per packet. SF12 uses ~32x more energy per packet than SF7.
### Formulas Used
**Receiver Sensitivity:**
\`\`\`
Sensitivity = -174 + 10*log10(BW_Hz) + NF + SNR_required
= -174 + 10*log10(${loraLinkBudget.bw * 1000}) + 6 + SNR(SF${loraLinkBudget.sf})
= ${loraLinkBudget.sensitivity} dBm
\`\`\`
**Link Budget:**
\`\`\`
Link Budget = TX Power + TX Antenna + RX Antenna - Sensitivity
= ${loraTxPower} + ${loraAntennaTx} + ${loraAntennaRx} - (${loraLinkBudget.sensitivity})
= ${loraLinkBudget.linkBudget} dB
\`\`\`
**Free Space Path Loss (at distance d km):**
\`\`\`
FSPL = 20*log10(d_km) + 20*log10(f_MHz) + 32.44
FSPL(1km, ${loraLinkBudget.freq}MHz) = ${loraLinkBudget.fspl1km} dB
\`\`\``