I’ve got an AllStarLink node running on a Raspberry Pi, and I’m exploring the possibility of using an ADALM-Pluto (PlutoSDR) OR a Pluto+ SDR (2 RX/TX) as the radio front-end instead of a traditional FM transceiver.
From what I understand, SoapySDR supports the Pluto, and could provide the I/Q interface layer — but it looks like I’d still need to handle demod/mod, audio bridging, PTT, COS, and connect that into AllStar (likely via ALSA loopback or a custom channel driver).
I'm trying to find out:
Has anyone in the community successfully used a PlutoSDR/Pluto+ with AllStarLink?
Is there a working example or repo that documents how to build a bridge between Pluto + GNU Radio (or similar) and AllStarLink?
Are there any existing projects or partial implementations that could help?
If no one has done this before — is anyone interested in collaborating with me to build and document this?
My goal:
To use the Pluto as a full-duplex or half-duplex SDR transceiver for an AllStar node — for experimentation and learning, and possibly for a compact SDR-linked repeater.
I’m envisioning something like:
Pluto SDR ⇄ SoapySDR ⇄ GNU Radio (FM demod/mod, PTT logic) ⇄ ALSA loopback ⇄ AllStarLink (app_rpt)
I'm comfortable with Linux, Raspberry Pi setups, Asterisk, C/C++ and some SDR basics — but would really appreciate help from anyone who's dealt with integrating SDRs into the AllStar stack.
Would love to hear your thoughts, tips, or even just a “don’t waste your time, here’s a better way”
Thanks in advance!
Callsign: OH2DTC Node: 66969 Location: Helsinki, Finland
I have a Pluto+ but had not got around to setting it up yet. It “should” be relatively easy to run through GNURadio and into the USRP channel driver. I wouldn’t think any additional SDR program beyond GNURadio would be needed. See also Set up a personal no-radio access point to Allstar - #20 by KV4FV and Chan_usrp & GNU Radio . USBRadio channel driver could also be used by changing the read() and write() calls in chan_usbradio.c to use a socket to GNURadio instead of to a USB device. LMK how it goes and I would be happy to help with some of these details.
All right, so far I have been able to get the Pluto+ running the Tezuka Firmware connected to the home network recognized on GNU radio, but, still stuggling to get the device setup using USRP. I am getting disillusioned with all the configurations. My understanding of app_rpt and USRP is pretty limited.
Has your Pluto+ arrived yet? I can definitely use a hand.
#!/usr/bin/env python3
import socket
import time
import numpy as np
import adi
import threading
from datetime import datetime
print("🎛️ Pluto-AllStar Bridge - FIXED BUFFER SIZE")
class PlutoBridge:
def __init__(self):
self.running = False
self.sdr = None
self.allstar_tx_port = 34001
self.allstar_rx_port = 32001
# Audio settings
self.audio_sample_rate = 8000
self.samples_per_chunk = 80 # 10ms at 8kHz
self.bytes_per_chunk = 160 # 80 samples * 2 bytes
# PlutoSDR settings - FIXED!
self.pluto_sample_rate = 1e6
self.pluto_buffer_size = 10000 # Much larger buffer!
# Statistics
self.rx_packets_sent = 0
self.tx_packets_received = 0
self.signal_level = 0
self.config = {
'rx_freq': 146.52e6,
'tx_freq': 146.52e6,
'rx_gain': 10,
'tx_gain': 0,
'pluto_ip': 'ip:192.168.1.100'
}
def start(self):
print("🚀 Starting bridge with fixed buffer size...")
# Initialize PlutoSDR with larger buffer
try:
self.sdr = adi.Pluto(self.config['pluto_ip'])
self.sdr.sample_rate = int(self.pluto_sample_rate)
self.sdr.rx_lo = int(self.config['rx_freq'])
self.sdr.tx_lo = int(self.config['tx_freq'])
self.sdr.rx_buffer_size = self.pluto_buffer_size # Larger buffer!
self.sdr.rx_hardwaregain_chan0 = self.config['rx_gain']
self.sdr.tx_hardwaregain_chan0 = self.config['tx_gain']
print(f"✅ PlutoSDR initialized with {self.pluto_buffer_size} sample buffer")
except Exception as e:
print(f"❌ PlutoSDR error: {e}")
return False
# Setup UDP sockets
try:
self.tx_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.tx_socket.bind(('127.0.0.1', self.allstar_tx_port))
print(f"📥 TX socket bound to {self.allstar_tx_port}")
self.rx_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print(f"📤 RX socket ready for {self.allstar_rx_port}")
except Exception as e:
print(f"❌ Socket error: {e}")
return False
self.running = True
self.sdr.tx_enable = True
print("🎯 Starting threads...")
# Start threads
rx_thread = threading.Thread(target=self.handle_rx)
rx_thread.daemon = True
rx_thread.start()
tx_thread = threading.Thread(target=self.handle_tx)
tx_thread.daemon = True
tx_thread.start()
monitor_thread = threading.Thread(target=self.monitor)
monitor_thread.daemon = True
monitor_thread.start()
return True
def handle_rx(self):
"""PlutoSDR → AllStar - FIXED VERSION"""
print("🎧 RX handler started")
allstar_rx_addr = ('127.0.0.1', self.allstar_rx_port)
# Audio buffer for accumulating samples
audio_buffer = np.array([], dtype=np.int16)
while self.running:
try:
# Get RF data from PlutoSDR
rf_data = self.sdr.rx()
# Calculate signal level
self.signal_level = np.sqrt(np.mean(np.abs(rf_data) ** 2))
if self.signal_level > 0.1: # We have signal
# Extract audio from real part
audio_raw = rf_data.real
audio_clean = audio_raw - np.mean(audio_raw)
# Normalize
max_val = np.max(np.abs(audio_clean))
if max_val > 0:
audio_normalized = (audio_clean / max_val) * 0.5
# Convert to 16-bit PCM
audio_16bit = (audio_normalized * 32767).astype(np.int16)
# Resample from 1MHz to 8kHz
decimation = 125 # 1,000,000 / 8,000 = 125
audio_8k = audio_16bit[::decimation]
# Add to buffer
audio_buffer = np.concatenate([audio_buffer, audio_8k])
# Send chunks when we have enough samples
while len(audio_buffer) >= self.samples_per_chunk:
# Take one chunk
chunk = audio_buffer[:self.samples_per_chunk]
audio_buffer = audio_buffer[self.samples_per_chunk:]
# Send the chunk
try:
self.rx_socket.sendto(chunk.tobytes(), allstar_rx_addr)
self.rx_packets_sent += 1
except Exception as e:
print(f"❌ RX send error: {e}")
time.sleep(0.01)
except Exception as e:
print(f"❌ RX handler error: {e}")
time.sleep(0.1)
def handle_tx(self):
"""AllStar → PlutoSDR"""
print("🎤 TX handler started")
while self.running:
try:
self.tx_socket.settimeout(0.5)
data, addr = self.tx_socket.recvfrom(1024)
if data and len(data) == self.bytes_per_chunk:
self.tx_packets_received += 1
# Convert to audio and transmit
audio_samples = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
rf_samples = audio_samples.astype(np.complex64) * 0.3
# Pad to match Pluto buffer size
if len(rf_samples) < len(self.sdr.rx()):
rf_samples = np.pad(rf_samples, (0, len(self.sdr.rx()) - len(rf_samples)))
self.sdr.tx(rf_samples)
except socket.timeout:
continue
except Exception as e:
print(f"❌ TX handler error: {e}")
def monitor(self):
"""Monitor with packet counting"""
while self.running:
signal_indicator = "🟢" if self.signal_level > 0.1 else "⚫"
print(f"\r{signal_indicator} Signal: {self.signal_level:.4f} | RX Packets: {self.rx_packets_sent} | TX Packets: {self.tx_packets_received}", end="", flush=True)
time.sleep(0.5)
def stop(self):
print("\n\n🛑 Stopping bridge...")
self.running = False
print("📊 FINAL STATS:")
print(f" RX Packets Sent: {self.rx_packets_sent}")
print(f" TX Packets Received: {self.tx_packets_received}")
if self.sdr:
self.sdr.tx_enable = False
if hasattr(self, 'tx_socket'):
self.tx_socket.close()
if hasattr(self, 'rx_socket'):
self.rx_socket.close()
# Run
bridge = PlutoBridge()
try:
if bridge.start():
print("\n✅ Bridge running with fixed buffer!")
print(" Transmit on your radio - RX packets should increase now!")
while bridge.running:
time.sleep(1)
else:
print("❌ Failed to start bridge")
except KeyboardInterrupt:
pass
finally:
bridge.stop()
But, I see the errors on asterisk for every packet sent
[2025-10-05 19:18:04.397] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.448] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.514] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.580] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.627] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.693] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.740] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.804] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.849] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.924] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:04.990] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.038] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.114] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.168] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.214] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.256] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.315] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
[2025-10-05 19:18:05.361] NOTICE[6306]: chan_usrp.c:500 usrp_xread: Channel usrp/127.0.0.1:34001:32001: Received packet from 127.0.0.1 with invalid data
I got the Pluto+ a couple months ago and was going to start on a next-generation repeater project that I have been discussing with N8GNJ and KA9Q but then started looking into some LinHT project development, and am currently busy with a few other projects that I should have wrapped up in a couple weeks. The Pluto+ is quite nice in that it can do 2 simultaneous full-duplex channels as well as wideband data and any conceivable data or voice mode. BTW there was another mention of USRP in Radtel RT-880 Development , is that the Python library you’re using?
if (memcmp(bufhdrp->eye, "USRP", 4)) {
ast_log(LOG_NOTICE, "Channel %s: Received packet from %s with invalid data\n", ...
Looks like it’s expecting the text “USRP” in the packet headers… If you wanted to debug further from the ASL side here are some notes on building ASL from source: GitHub - davidgsd/app_rpt at doc