Compression & error correction for Meshtastic — extend your range by 2x
MeshXT preprocesses your Meshtastic messages with intelligent compression and forward error correction, allowing you to send the same data using higher spreading factors for dramatically more range.
🔧 Want to flash your device directly? This is the JavaScript developer library. For a ready-to-flash firmware, see Meshtastic-MeshXT-Firmware — plug in USB, run 4 commands, done.
# Install globally for CLI use
npm install -g meshxt
# Or run directly with npx (no install needed)
npx meshxt encode "Hello world"
# Or add to your project as a dependency
npm install meshxtgit clone https://github.com/DarrenEdwards111/MeshXT.git
cd MeshXT
npm test # Run 70 tests
npm link # Make CLI available globally (optional)Your Message ──→ Compress ──→ FEC Encode ──→ Packet Frame ──→ LoRa Radio
(40-50%) (Reed-Solomon) (2-byte header)
smaller error-proof fits 237 bytes
The key insight: smaller packets = higher spreading factors = more range.
If compression cuts your message in half, you can bump up the spreading factor by one step — roughly doubling your range with the same airtime.
- 🗜️ Smaz Compression — Short-string compression optimised for English text (15-50% savings)
- 📖 Codebook Templates — 74 predefined messages encoded in 1-9 bytes ("I'm OK" = 1 byte)
- 🛡️ Reed-Solomon FEC — Corrects up to 32 corrupted bytes per packet
- 📡 Adaptive SF Selection — Auto-picks optimal spreading factor, bandwidth, and coding rate
- 📦 Packet Framing — Complete packet format that fits Meshtastic's 237-byte limit
- 🔧 CLI Tool — Encode, decode, benchmark, and estimate range from the command line
- 🚫 Zero Dependencies — Pure Node.js, nothing to install
# Encode a message (shows compression stats + range estimate)
meshxt encode "Are you free for dinner Thursday?"
# Encode with specific options
meshxt encode "Need help" --fec high --compress smaz
# Decode a hex packet
meshxt decode <hex_string>
# Benchmark compression on a message
meshxt bench "Need help at the old bridge, heading south"
# Estimate range for given LoRa parameters
meshxt range --sf 12 --bw 125 --power 14 --antenna 6
# List all codebook templates
meshxt codebook
# Show help
meshxt help$ meshxt encode "Are you free for dinner Thursday?"
📡 MeshXT Encode
─────────────────────────────────
Original: 33 bytes
Compressed: 24 bytes (27.3% saved)
+ FEC (medium): +32 bytes
+ Header: +2 bytes
Total packet: 58 bytes / 237 max
─────────────────────────────────
Recommended: SF12 BW500kHz CR8
Est. range: ~4.3km (2mi)
─────────────────────────────────
Hex: 1120...
$ meshxt range --sf 12 --bw 125 --power 14 --antenna 6
📡 MeshXT Range Estimate
─────────────────────────────────
SF: 12
Bandwidth: 125 kHz
TX Power: 14 dBm
Antenna gain: 6 dBi
─────────────────────────────────
Est. range: ~7.3km (4mi)
Link budget: 158.5dB
Sensitivity: -135.5dBm
const meshxt = require('meshxt');
// ── Compression ────────────────────────────
const compressed = meshxt.compress('Are you free for dinner Thursday?');
console.log(`Compressed: ${compressed.length} bytes`);
const original = meshxt.decompress(compressed);
console.log(`Decompressed: ${original}`);
// ── Codebook (ultra-compact messages) ──────
// Simple templates (1 byte)
const sos = meshxt.codebook.encode('sos'); // 1 byte
const ok = meshxt.codebook.encode('ok'); // 1 byte
const omw = meshxt.codebook.encode('on_my_way'); // 1 byte
// Parameterised templates
const loc = meshxt.codebook.encode('location', { lat: 51.5074, lon: -3.1791 }); // 9 bytes
const eta = meshxt.codebook.encode('eta', { minutes: 15 }); // 2 bytes
const weather = meshxt.codebook.encode('weather', { type: 'rain' }); // 2 bytes
const battery = meshxt.codebook.encode('battery', { percent: 42 }); // 2 bytes
// Decode
const decoded = meshxt.codebook.decode(loc);
console.log(decoded.text); // "At location [51.507400, -3.179100]"
console.log(decoded.params); // { lat: 51.5074, lon: -3.1791 }
// List all 74 templates
const templates = meshxt.codebook.listTemplates();
// ── Forward Error Correction ───────────────
const data = meshxt.compress('Important message');
const protected_ = meshxt.fec.encode(data, 'medium'); // Add 32 parity bytes
const recovered = meshxt.fec.decode(protected_, 'medium'); // Corrects up to 16 errors
// FEC levels: 'low' (8 errors), 'medium' (16 errors), 'high' (32 errors)
console.log(`Parity bytes: ${meshxt.fec.parityBytes('medium')}`); // 32
console.log(`Max corrections: ${meshxt.fec.maxCorrectableErrors('medium')}`); // 16
// ── Full Packet (compression + FEC + framing) ─
const { createPacket, parsePacket } = require('meshxt/src/packet');
const result = createPacket('Hello from MeshXT!', {
compression: 'smaz', // 'smaz', 'codebook', or 'none'
fec: 'low', // 'low', 'medium', 'high', or 'none'
});
console.log(`Packet size: ${result.packet.length} bytes`);
console.log(`Compression ratio: ${(result.stats.compressionRatio * 100).toFixed(1)}%`);
const parsed = parsePacket(result.packet);
console.log(`Message: ${parsed.message}`); // "Hello from MeshXT!"
// ── Adaptive LoRa Parameter Selection ──────
const rec = meshxt.adaptive.recommend(result.packet.length);
console.log(`Recommended: SF${rec.sf} BW${rec.bw}kHz`);
console.log(`Est. range: ${rec.rangeKm}km`);
console.log(`Airtime: ${rec.airtimeMs}ms`);
// Direct range estimation
const range = meshxt.adaptive.rangeEstimate(12, 125, 14, 6);
console.log(`Range: ${range.rangeKm}km`);
console.log(`Link budget: ${range.linkBudget}dB`);
console.log(`Sensitivity: ${range.sensitivity}dBm`);If you're building a Node.js app that talks to a Meshtastic device over serial or BLE, here's how to integrate MeshXT:
const meshxt = require('meshxt');
const { SerialPort } = require('serialport'); // npm install serialport
// Connect to your Meshtastic device
const port = new SerialPort({ path: '/dev/ttyUSB0', baudRate: 115200 });
// Sending: compress before sending via Meshtastic serial API
function sendMessage(text) {
const packet = meshxt.createPacket(text, {
compression: 'smaz',
fec: 'low'
});
// Send packet.packet bytes via Meshtastic protobuf serial API
// The receiving end needs MeshXT to decode
console.log(`Sent "${text}" as ${packet.packet.length} bytes (was ${text.length})`);
}
// Receiving: decode incoming MeshXT packets
function onReceive(data) {
const parsed = meshxt.parsePacket(data);
if (parsed.valid) {
console.log(`Received: ${parsed.message}`);
}
}Note: Both sender and receiver need MeshXT. For device-to-device without a computer, use the combined firmware instead.
| Configuration | Typical Range | Notes |
|---|---|---|
| Stock Meshtastic SF12 | ~50 km | No compression |
| + MeshXT Smaz compression | ~80 km | 50% smaller → higher SF viable |
| + Reed-Solomon FEC | ~90 km | Decodes at lower SNR |
| + Directional antenna | ~200 km | 10dBi Yagi |
| Combined | ~200 km / 125 mi | All optimisations |
| Message | Original | Compressed | Savings |
|---|---|---|---|
| "Roger that, heading to your location now" | 40 bytes | 19 bytes | 52% |
| "Can you call me when you get this?" | 34 bytes | 21 bytes | 38% |
| "Going to the store, do you need anything?" | 41 bytes | 26 bytes | 37% |
| "The weather is looking good today" | 33 bytes | 23 bytes | 30% |
| "On my way home now, be there in 20 minutes" | 42 bytes | 30 bytes | 29% |
| Message | Bytes |
|---|---|
| "I'm OK" | 1 |
| "SOS" | 1 |
| "On my way" | 1 |
| "Need help" | 1 |
| "ETA 15 minutes" | 2 |
| "Weather: rain" | 2 |
| "Battery 42%" | 2 |
| "At location [lat, lon]" | 9 |
74 templates covering emergencies, status, navigation, weather, and more.
┌─────────────────────────┬─────────────────────────┬───────────────────┐
│ Header (2B) │ Payload (variable) │ FEC Parity │
├────────┬────────────────┼─────────────────────────┼───────────────────┤
│ Byte 0 │ Byte 1 │ Compressed message data │ Reed-Solomon ECC │
│ │ │ │ │
│ VVVV │ FFFF │ │ 16/32/64 bytes │
│ CCCC │ xxxx │ │ │
└────────┴────────────────┴─────────────────────────┴───────────────────┘
V = Version (4 bits) F = FEC level (4 bits)
C = Compression (4 bits) x = Flags (4 bits)
Total: ≤ 237 bytes (Meshtastic max payload)
| Level | Parity Bytes | Corrects Up To | Overhead |
|---|---|---|---|
| Low | 16 | 8 byte errors | ~10% |
| Medium | 32 | 16 byte errors | ~20% |
| High | 64 | 32 byte errors | ~40% |
═══════════════════════════════════════════
C++ Firmware Compile & Runtime Test Results
═══════════════════════════════════════════
Compression Test:
✅ Compress: 33 bytes → 24 bytes (27% saved)
✅ Decompress: "Are you free for dinner Thursday?"
✅ Roundtrip: Perfect match
FEC Test:
✅ Encode: 24 bytes → 40 bytes (+16 parity)
✅ Decode: 24 bytes (clean, no errors)
✅ Roundtrip: Perfect match
Full Packet Test:
✅ Create: 33 bytes → 42 bytes (fits in 237 max)
✅ Parse: "Are you free for dinner Thursday?"
✅ Roundtrip: Perfect match
✅ Valid: YES
═══════════════════════════════════════════
All tests passed — zero warnings, zero errors.
Node.js: 70/70 tests passing.
═══════════════════════════════════════════
MeshXT is optimised for the UK/EU 868 MHz ISM band:
- Max ERP: 25 mW (14 dBm)
- Duty cycle: 1% (g1 sub-band)
- Adaptive SF selection respects duty cycle limits
⚡ Use the combined firmware repo: Meshtastic-MeshXT-Firmware
MeshXT is a Meshtastic module — it needs the full Meshtastic firmware to run. You can't flash MeshXT on its own. The combined firmware repo gives you Meshtastic + MeshXT in one flash.
pip install platformio
git clone https://github.com/DarrenEdwards111/Meshtastic-MeshXT-Firmware.git
cd Meshtastic-MeshXT-Firmware
bash scripts/setup.sh
bash scripts/flash.sh heltec-v3T-Beam, RAK4631, T-Deck, T-Echo — see the full device list and instructions.
This repo contains the MeshXT module source code in firmware/src/ (8 files, ~967 lines). These are the files that get integrated into the Meshtastic firmware build. For manual integration details, see the firmware repo.
meshxt/
├── package.json # Zero dependencies
├── README.md
├── LICENSE # Apache 2.0
├── src/
│ ├── index.js # Main entry point
│ ├── compress.js # Smaz-style text compression
│ ├── codebook.js # 74 predefined message templates
│ ├── fec.js # Reed-Solomon GF(2^8) FEC
│ ├── adaptive.js # LoRa parameter optimisation
│ ├── packet.js # Packet framing (header + payload + FEC)
│ └── utils.js # Helpers
├── bin/
│ └── longshot.js # CLI tool
└── test/
└── test.js # 70 tests, all passing
- Node.js >= 16.0.0
- No external dependencies
Apache 2.0 — Copyright 2026 Mikoshi Ltd
PRs welcome! Key areas for contribution:
- C/C++ port for direct Meshtastic firmware integration
- Improved compression codebook for non-English languages
- Adaptive FEC that adjusts based on link quality
- Real-world range testing and validation
- Python port for MicroPython on ESP32
- 🐛 Bug reports — GitHub Issues
- 💡 Ideas, questions & development discussion — GitHub Discussions
- 📧 Contact — mikoshiuk@gmail.com
