Chan_usrp & GNU Radio

Edit: I was able to figure this out through some trial and error. I’ll post an update soon.

Has anyone been successful in using a GNU Radio USRP Sink/Source block to connect to AllStar via chan_usrp? I’m running into some issues.

GNU Radio doesn’t let you specify the port for the USRP block, so it defaults to whatever the UHD USRP library is set to. If I manually set that port in ASL, it does attempt to connect, but it gives a bad packet error from chan_usrp:

[Mar 25 18:15:48] NOTICE[18154] chan_usrp.c: Received packet length 16 too short
[Mar 25 18:16:01] NOTICE[18154] chan_usrp.c: Received packet from 10.0.1.200 with invalid data

I was thinking about taking the following approach and just sending the data back to another Python script, but it seems a bit silly to have to do that:
https://wiki.gnuradio.org/index.php/Understanding_ZMQ_Blocks

Basically, it just receives and sends data over a local TCP socket so you can pipe the desired data to and from GNU radio. This approach WILL work from what I can tell, but it seems… dirty to me I guess. Lol.

Here’s the gist of the program I’m writing. I’m stripping down the code from here:
GitHub - DVSwitch/USRP_Client to just the basic TX and RX goodies. I’ve tested it and it works great over chan_usrp. You basically get a string of 320 bytes per loop, and can send that to the sound card/to ASL. But I want to send it to GNU radio for further processing. For RX from AllStar, it’ll pretty much just send it straight to the sound card for now. For TX from the microphone/radio, it’ll pass it through GNU radio to do CTCSS, squelch, and all of the other band/low/high pass filtering, then send it over to AllStar via USRP.

This is also to add SDR capabilities to AllStar to allow a cheap way to do full duplex. This would be a really cool feature – imagine having the ability to plug in a ~$25 SDR and your node is then capable of full duplex (with some filtering or crossband). An additional feature this could bring is the ability to do Voter with SDRs – each Voter site/instance only needs an SDR and a Pi.

Is there an easy way to just send that byte stream over to GNU radio in the same script?

If I take the ZeroMQ approach, any recommendations on how the flow chart might look? I’m still trying to figure out how all of the different sampling, resampling, multipliers, selectors, signal source, and all that plays into getting out a clean audio signal, so any insight there is appreciated.

Cheers!
Rob
ASL Dev Team

Hello!
I have a pluto+ SDR and I have been trying to connect it to ASL3 using a python script.
I am also running into the same issue as you “Received packet from 127.0.0.1 with invalid data.
Here’s my code to receive and transmit from/to allstar.

#!/usr/bin/env python3
import socket
import time
import numpy as np
import adi
import threading
from datetime import datetime

print("🎛️  Pluto-AllStar Bridge - CORRECT AUDIO FORMAT")

class PlutoBridge:
    def __init__(self):
        self.running = False
        self.sdr = None
        
        self.allstar_tx_port = 34001
        self.allstar_rx_port = 32001
        
        # CORRECT AUDIO FORMAT (based on common AllStar config)
        self.audio_sample_rate = 8000
        self.samples_per_chunk = 80    # 10ms at 8kHz
        self.bytes_per_chunk = 160     # 80 samples * 2 bytes (16-bit)
        
        # PlutoSDR settings
        self.pluto_sample_rate = 1e6
        self.pluto_buffer_size = 10000
        
        # 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 correct audio format...")
        print(f"🎵 Expected format: 16-bit signed PCM, {self.audio_sample_rate}Hz, mono")
        print(f"📦 Packet size: {self.bytes_per_chunk} bytes ({self.samples_per_chunk} samples)")
        
        # Initialize PlutoSDR
        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
            self.sdr.rx_hardwaregain_chan0 = self.config['rx_gain']
            self.sdr.tx_hardwaregain_chan0 = self.config['tx_gain']
            print("✅ PlutoSDR initialized")
        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 - CORRECT AUDIO FORMAT"""
        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 (simple AM demodulation)
                    audio_raw = rf_data.real
                    audio_clean = audio_raw - np.mean(audio_raw)
                    
                    # Normalize carefully to avoid clipping
                    max_val = np.max(np.abs(audio_clean))
                    if max_val > 0:
                        # Use conservative normalization
                        audio_normalized = (audio_clean / max_val) * 0.3
                        
                        # Convert to 16-bit signed PCM (-32768 to 32767)
                        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:]
                            
                            # Verify chunk size and send
                            if len(chunk) == self.samples_per_chunk:
                                try:
                                    packet_data = chunk.tobytes()
                                    if len(packet_data) == self.bytes_per_chunk:
                                        self.rx_socket.sendto(packet_data, allstar_rx_addr)
                                        self.rx_packets_sent += 1
                                    else:
                                        print(f"⚠️  Wrong packet size: {len(packet_data)} bytes")
                                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:
                    if len(data) == self.bytes_per_chunk:
                        self.tx_packets_received += 1
                        
                        # Convert from 16-bit PCM to float
                        audio_samples = np.frombuffer(data, dtype=np.int16).astype(np.float32) / 32768.0
                        
                        # Simple modulation for transmission
                        rf_samples = audio_samples.astype(np.complex64) * 0.3
                        
                        # Pad to match Pluto buffer size
                        current_rf_data = self.sdr.rx()  # Get current buffer size
                        if len(rf_samples) < len(current_rf_data):
                            rf_samples = np.pad(rf_samples, (0, len(current_rf_data) - len(rf_samples)))
                        
                        self.sdr.tx(rf_samples)
                    else:
                        print(f"⚠️  TX: Wrong packet size: {len(data)} bytes (expected {self.bytes_per_chunk})")
                        
            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: {self.rx_packets_sent} | TX: {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 correct audio format!")
        print("   Watch Asterisk logs - 'invalid data' errors should stop")
        while bridge.running:
            time.sleep(1)
    else:
        print("❌ Failed to start bridge")
except KeyboardInterrupt:
    pass
finally:
    bridge.stop()

I’d also appreciate any direction or help with this, this should not be that hard!