udp transport option for tx/rx
This commit is contained in:
parent
62e2ef92f0
commit
3a0901e646
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56120",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "rc",
|
||||||
|
"frequency": 925000000,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "p25 cc",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "symbols:sym-cc925.dat",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56124",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "rc",
|
||||||
|
"frequency": 924900000,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "p25 vc",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand4.raw",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"args": "udp:127.0.0.1:25252",
|
||||||
|
"frequency": 924950000,
|
||||||
|
"gains": "",
|
||||||
|
"name": "udp",
|
||||||
|
"offset": 0,
|
||||||
|
"ppm": 0,
|
||||||
|
"rate": 480000,
|
||||||
|
"tunable": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56124",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "nxdn",
|
||||||
|
"frequency": 442112500,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "nxdn48",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand0.raw",
|
||||||
|
"symbol_rate": 2400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56128",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "rrc",
|
||||||
|
"frequency": 442187500,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "dmr",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand1.raw",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56132",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "gmsk",
|
||||||
|
"frequency": 442262500,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "dstar",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand2.raw",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56136",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "rrc",
|
||||||
|
"frequency": 442337500,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "ysf",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand3.raw",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"demod_type": "fsk4",
|
||||||
|
"destination": "udp://127.0.0.1:56120",
|
||||||
|
"excess_bw": 0.2,
|
||||||
|
"filter_type": "rc",
|
||||||
|
"frequency": 442412500,
|
||||||
|
"if_rate": 24000,
|
||||||
|
"name": "p25",
|
||||||
|
"plot": "datascope",
|
||||||
|
"source": "/home/mhp/rand4.raw",
|
||||||
|
"symbol_rate": 4800
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"args": "udp:127.0.0.1:25252",
|
||||||
|
"frequency": 442262500,
|
||||||
|
"gains": "",
|
||||||
|
"name": "udp",
|
||||||
|
"offset": 0,
|
||||||
|
"ppm": 0,
|
||||||
|
"rate": 480000,
|
||||||
|
"tunable": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -77,6 +77,8 @@ class device(object):
|
||||||
self.init_audio(config)
|
self.init_audio(config)
|
||||||
elif config['args'].startswith('file:'):
|
elif config['args'].startswith('file:'):
|
||||||
self.init_file(config)
|
self.init_file(config)
|
||||||
|
elif config['args'].startswith('udp:'):
|
||||||
|
self.init_udp(config)
|
||||||
else:
|
else:
|
||||||
self.init_osmosdr(config)
|
self.init_osmosdr(config)
|
||||||
|
|
||||||
|
@ -98,6 +100,16 @@ class device(object):
|
||||||
self.src = blocks.multiply_const_ff(gain)
|
self.src = blocks.multiply_const_ff(gain)
|
||||||
self.tb.connect(src, self.src)
|
self.tb.connect(src, self.src)
|
||||||
|
|
||||||
|
def init_udp(self, config):
|
||||||
|
hostinfo = config['args'].split(':')
|
||||||
|
hostname = hostinfo[1]
|
||||||
|
udp_port = int(hostinfo[2])
|
||||||
|
bufsize = 32000 # might try enlarging this if packet loss
|
||||||
|
self.src = blocks.udp_source(gr.sizeof_gr_complex, hostname, udp_port, payload_size = bufsize)
|
||||||
|
self.ppm = 0
|
||||||
|
self.frequency = config['frequency']
|
||||||
|
self.offset = 0
|
||||||
|
|
||||||
def init_osmosdr(self, config):
|
def init_osmosdr(self, config):
|
||||||
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
|
speeds = [250000, 1000000, 1024000, 1800000, 1920000, 2000000, 2048000, 2400000, 2560000]
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,29 @@ WIRESHARK_PORT = 23456
|
||||||
_def_interval = 3.0 # sec
|
_def_interval = 3.0 # sec
|
||||||
_def_file_dir = '../www/images'
|
_def_file_dir = '../www/images'
|
||||||
|
|
||||||
|
class udp_source_c(gr.hier_block2):
|
||||||
|
def __init__(self, hostname, udp_port):
|
||||||
|
gr.hier_block2.__init__(self, "udp_source_c",
|
||||||
|
gr.io_signature(0, 0, 0), # Input signature
|
||||||
|
gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
|
||||||
|
bufsize = 32000
|
||||||
|
self.src = blocks.udp_source(gr.sizeof_gr_complex, hostname, udp_port, payload_size = bufsize)
|
||||||
|
self.sample_rate = 0
|
||||||
|
self.connect(self.src, self)
|
||||||
|
|
||||||
|
def set_center_freq(self, f):
|
||||||
|
sys.stderr.write('udp source: set_center_freq(%s) ignored\n' % f)
|
||||||
|
|
||||||
|
def set_bandwidth(self, f):
|
||||||
|
sys.stderr.write('udp source: set_bandwidth(%s) ignored\n' % f)
|
||||||
|
|
||||||
|
def set_sample_rate(self, f):
|
||||||
|
self.sample_rate = f
|
||||||
|
return f
|
||||||
|
|
||||||
|
def set_antenna(self, a):
|
||||||
|
sys.stderr.write('udp source: set_antenna(%s) ignored\n' % a)
|
||||||
|
|
||||||
# The P25 receiver
|
# The P25 receiver
|
||||||
#
|
#
|
||||||
class p25_rx_block (gr.top_block):
|
class p25_rx_block (gr.top_block):
|
||||||
|
@ -109,7 +132,7 @@ class p25_rx_block (gr.top_block):
|
||||||
self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None}
|
self.last_freq_params = {'freq' : 0.0, 'tgid' : None, 'tag' : "", 'tdma' : None}
|
||||||
|
|
||||||
self.src = None
|
self.src = None
|
||||||
if (not options.input) and (not options.audio) and (not options.audio_if):
|
if (not options.input) and (not options.audio) and (not options.audio_if) and (not options.args.startswith('udp:')):
|
||||||
# check if osmocom is accessible
|
# check if osmocom is accessible
|
||||||
try:
|
try:
|
||||||
import osmosdr
|
import osmosdr
|
||||||
|
@ -141,6 +164,12 @@ class p25_rx_block (gr.top_block):
|
||||||
|
|
||||||
if options.freq_corr:
|
if options.freq_corr:
|
||||||
self.src.set_freq_corr(options.freq_corr)
|
self.src.set_freq_corr(options.freq_corr)
|
||||||
|
elif (not options.input) and (not options.audio) and (not options.audio_if) and options.args.startswith('udp:'):
|
||||||
|
hostinfo = options.args.split(':')
|
||||||
|
hostname = hostinfo[1]
|
||||||
|
udp_port = int(hostinfo[2])
|
||||||
|
self.src = udp_source_c(hostname, udp_port)
|
||||||
|
sys.stderr.write('started udp listener: %s %s\n' % (hostname, udp_port))
|
||||||
|
|
||||||
if options.audio:
|
if options.audio:
|
||||||
self.channel_rate = 48000
|
self.channel_rate = 48000
|
||||||
|
|
|
@ -24,12 +24,13 @@
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Transmit four simultaneous RF channels (dmr, p25, dstar, and ysf)
|
Transmit M simultaneous RF channels via N devices
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import math
|
import math
|
||||||
|
import json
|
||||||
from gnuradio import gr, gru, audio, eng_notation
|
from gnuradio import gr, gru, audio, eng_notation
|
||||||
from gnuradio import filter, blocks, analog, digital
|
from gnuradio import filter, blocks, analog, digital
|
||||||
from gnuradio.eng_option import eng_option
|
from gnuradio.eng_option import eng_option
|
||||||
|
@ -43,13 +44,28 @@ from math import pi
|
||||||
|
|
||||||
from op25_c4fm_mod import p25_mod_bf
|
from op25_c4fm_mod import p25_mod_bf
|
||||||
|
|
||||||
class pipeline(gr.hier_block2):
|
_def_symbol_rate = 4800
|
||||||
def __init__(self, protocol=None, config_file=None, mod_adjust=None, gain_adjust=None, output_gain=None, if_freq=0, if_rate=0, verbose=0, fullrate_mode=False, sample_rate=0, bt=0, alt_input=None):
|
_def_bt = 0.5
|
||||||
gr.hier_block2.__init__(self, "dv_modulator",
|
|
||||||
gr.io_signature(1, 1, gr.sizeof_short), # Input signature
|
def byteify(input): # thx so
|
||||||
gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
|
if sys.version[0] != '2': # hack, must be a better way
|
||||||
|
return input
|
||||||
|
if isinstance(input, dict):
|
||||||
|
return {byteify(key): byteify(value)
|
||||||
|
for key, value in input.iteritems()}
|
||||||
|
elif isinstance(input, list):
|
||||||
|
return [byteify(element) for element in input]
|
||||||
|
elif isinstance(input, unicode):
|
||||||
|
return input.encode('utf-8')
|
||||||
|
else:
|
||||||
|
return input
|
||||||
|
|
||||||
|
class pipeline_sb(gr.hier_block2):
|
||||||
|
def __init__(self, protocol=None, config_file=None, gain_adjust=None, verbose=0, fullrate_mode=False, alt_input=None):
|
||||||
|
gr.hier_block2.__init__(self, "dv_encoder",
|
||||||
|
gr.io_signature(1, 1, gr.sizeof_short), # Input signature
|
||||||
|
gr.io_signature(1, 1, gr.sizeof_char)) # Output signature
|
||||||
|
|
||||||
from dv_tx import RC_FILTER
|
|
||||||
if protocol == 'dmr':
|
if protocol == 'dmr':
|
||||||
assert config_file
|
assert config_file
|
||||||
ENCODER = op25_repeater.ambe_encoder_sb(verbose)
|
ENCODER = op25_repeater.ambe_encoder_sb(verbose)
|
||||||
|
@ -77,7 +93,17 @@ class pipeline(gr.hier_block2):
|
||||||
assert config_file
|
assert config_file
|
||||||
ENCODER = op25_repeater.nxdn_tx_sb(verbose, config_file, protocol == 'nxdn96')
|
ENCODER = op25_repeater.nxdn_tx_sb(verbose, config_file, protocol == 'nxdn96')
|
||||||
ENCODER.set_gain_adjust(gain_adjust)
|
ENCODER.set_gain_adjust(gain_adjust)
|
||||||
|
if protocol == 'dmr':
|
||||||
|
self.connect(DMR, self)
|
||||||
|
else:
|
||||||
|
self.connect(self, ENCODER, self)
|
||||||
|
|
||||||
|
class mod_pipeline_bc(gr.hier_block2):
|
||||||
|
def __init__(self, protocol=None, mod_adjust=None, output_gain=None, if_freq=0, if_rate=0, verbose=0, sample_rate=0, bt=0):
|
||||||
|
from dv_tx import RC_FILTER
|
||||||
|
gr.hier_block2.__init__(self, "dv_modulator",
|
||||||
|
gr.io_signature(1, 1, gr.sizeof_char), # Input signature
|
||||||
|
gr.io_signature(1, 1, gr.sizeof_gr_complex)) # Output signature
|
||||||
MOD = p25_mod_bf(output_sample_rate = sample_rate, dstar = (protocol == 'dstar'), bt = bt, rc = RC_FILTER[protocol])
|
MOD = p25_mod_bf(output_sample_rate = sample_rate, dstar = (protocol == 'dstar'), bt = bt, rc = RC_FILTER[protocol])
|
||||||
|
|
||||||
AMP = blocks.multiply_const_ff(output_gain)
|
AMP = blocks.multiply_const_ff(output_gain)
|
||||||
|
@ -87,70 +113,85 @@ class pipeline(gr.hier_block2):
|
||||||
|
|
||||||
FM_MOD = analog.frequency_modulator_fc (k * mod_adjust)
|
FM_MOD = analog.frequency_modulator_fc (k * mod_adjust)
|
||||||
|
|
||||||
if protocol == 'dmr':
|
|
||||||
self.connect(DMR, MOD)
|
|
||||||
else:
|
|
||||||
self.connect(self, ENCODER, MOD)
|
|
||||||
|
|
||||||
INTERP = filter.rational_resampler_fff(if_rate // sample_rate, 1)
|
INTERP = filter.rational_resampler_fff(if_rate // sample_rate, 1)
|
||||||
|
|
||||||
MIXER = blocks.multiply_cc()
|
MIXER = blocks.multiply_cc()
|
||||||
LO = analog.sig_source_c(if_rate, analog.GR_SIN_WAVE, if_freq, 1.0, 0)
|
LO = analog.sig_source_c(if_rate, analog.GR_SIN_WAVE, if_freq, 1.0, 0)
|
||||||
|
|
||||||
self.connect(MOD, AMP, INTERP, FM_MOD, (MIXER, 0))
|
self.connect(self, MOD, AMP, INTERP, FM_MOD, (MIXER, 0))
|
||||||
self.connect(LO, (MIXER, 1))
|
self.connect(LO, (MIXER, 1))
|
||||||
self.connect(MIXER, self)
|
self.connect(MIXER, self)
|
||||||
|
|
||||||
class my_top_block(gr.top_block):
|
class device(object):
|
||||||
def __init__(self):
|
def __init__(self, config, tb):
|
||||||
|
self.name = config['name']
|
||||||
|
self.sample_rate = config['rate']
|
||||||
|
self.args = config['args']
|
||||||
|
self.frequency = config['frequency']
|
||||||
|
self.tb = tb
|
||||||
|
self.sum = blocks.add_cc()
|
||||||
|
self.sum_count = 0
|
||||||
|
self.output_throttle = None
|
||||||
|
|
||||||
|
def get_sum_p(self):
|
||||||
|
seq = self.sum_count
|
||||||
|
self.sum_count += 1
|
||||||
|
return (self.sum, seq)
|
||||||
|
|
||||||
|
class channel(object):
|
||||||
|
def __init__(self, config, dev, verbosity, msgq = None):
|
||||||
|
sys.stderr.write('channel (dev %s): %s\n' % (dev.name, config))
|
||||||
|
self.device = dev
|
||||||
|
self.name = config['name']
|
||||||
|
self.symbol_rate = _def_symbol_rate
|
||||||
|
if 'symbol_rate' in config.keys():
|
||||||
|
self.symbol_rate = config['symbol_rate']
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def get_protocol(chan):
|
||||||
|
# try to autodetect protocol, return None if failed
|
||||||
|
if chan.config['filter_type'].startswith('nxdn'):
|
||||||
|
if chan.symbol_rate == 2400:
|
||||||
|
return 'nxdn48'
|
||||||
|
else:
|
||||||
|
return 'nxdn96'
|
||||||
|
elif chan.config['filter_type'] == 'rc':
|
||||||
|
return 'p25'
|
||||||
|
elif chan.config['filter_type'] == 'gmsk':
|
||||||
|
return 'dstar'
|
||||||
|
elif chan.config['filter_type'] != 'rrc':
|
||||||
|
return None
|
||||||
|
if 'dmr' in chan.config['name'].lower():
|
||||||
|
return 'dmr'
|
||||||
|
if 'ysf' in chan.config['name'].lower():
|
||||||
|
return 'ysf'
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_source(config, k, audio_source):
|
||||||
|
if k not in config.keys():
|
||||||
|
return None
|
||||||
|
s = config[k]
|
||||||
|
if s.startswith('audio:'):
|
||||||
|
return audio_source
|
||||||
|
elif s.startswith('udp:'): # S16_LE at 8000 rate
|
||||||
|
hostinfo = s.split(':')
|
||||||
|
hostname = hostinfo[1]
|
||||||
|
udp_port = int(hostinfo[2])
|
||||||
|
bufsize = 32000
|
||||||
|
return blocks.udp_source(gr.sizeof_short, hostname, udp_port, payload_size = bufsize)
|
||||||
|
else:
|
||||||
|
return blocks.file_source(gr.sizeof_short, s, repeat = True)
|
||||||
|
|
||||||
|
class tx_block(gr.top_block):
|
||||||
|
def __init__(self, verbosity, config):
|
||||||
|
self.verbosity = verbosity
|
||||||
gr.top_block.__init__(self)
|
gr.top_block.__init__(self)
|
||||||
parser = OptionParser(option_class=eng_option)
|
|
||||||
|
|
||||||
parser.add_option("-a", "--args", type="string", default="", help="device args")
|
self.configure_devices(config['devices'])
|
||||||
parser.add_option("-A", "--do-audio", action="store_true", default=False, help="live input audio")
|
self.configure_channels(config['channels'])
|
||||||
parser.add_option("-b", "--bt", type="float", default=0.5, help="specify bt value")
|
|
||||||
parser.add_option("-f", "--file", type="string", default=None, help="specify the input file (mono 8000 sps S16_LE)")
|
|
||||||
parser.add_option("-g", "--gain", type="float", default=1.0, help="input gain")
|
|
||||||
parser.add_option("-i", "--if-rate", type="int", default=480000, help="output rate to sdr")
|
|
||||||
parser.add_option("-I", "--audio-input", type="string", default="", help="pcm input device name. E.g., hw:0,0 or /dev/dsp")
|
|
||||||
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
|
|
||||||
parser.add_option("-o", "--if-offset", type="float", default=100000, help="channel spacing (Hz)")
|
|
||||||
parser.add_option("-q", "--frequency-correction", type="float", default=0.0, help="ppm")
|
|
||||||
parser.add_option("-Q", "--frequency", type="float", default=0.0, help="Hz")
|
|
||||||
parser.add_option("-r", "--repeat", action="store_true", default=False, help="input file repeat")
|
|
||||||
parser.add_option("-R", "--fullrate-mode", action="store_true", default=False, help="ysf fullrate")
|
|
||||||
parser.add_option("-s", "--modulator-rate", type="int", default=48000, help="must be submultiple of IF rate")
|
|
||||||
parser.add_option("-S", "--alsa-rate", type="int", default=48000, help="sound source/sink sample rate")
|
|
||||||
parser.add_option("-v", "--verbose", type="int", default=0, help="additional output")
|
|
||||||
(options, args) = parser.parse_args()
|
|
||||||
|
|
||||||
assert options.file # input file name (-f filename) required
|
audio_chans = [chan for chan in self.channels if chan.config['source'].startswith('audio:')]
|
||||||
|
if len(audio_chans):
|
||||||
f1 = float(options.if_rate) / options.modulator_rate
|
|
||||||
i1 = int(options.if_rate / options.modulator_rate)
|
|
||||||
if f1 - i1 > 1e-3:
|
|
||||||
print ('*** Error, sdr rate %d not an integer multiple of modulator rate %d - ratio=%f' % (options.if_rate, options.modulator_rate, f1))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
protocols = 'nxdn48 dmr dstar ysf p25'.split()
|
|
||||||
|
|
||||||
start_freq = options.frequency
|
|
||||||
end_freq = options.frequency + options.if_offset * (len(protocols)-1)
|
|
||||||
tune_freq = (start_freq + end_freq) // 2
|
|
||||||
print ('start %d end %d center tune %d' % (start_freq, end_freq, tune_freq))
|
|
||||||
|
|
||||||
bw = options.if_offset * len(protocols) + 50000
|
|
||||||
if bw > options.if_rate:
|
|
||||||
print ('*** Error, a %d Hz band is required for %d channels and guardband.' % (bw, len(protocols)))
|
|
||||||
print ('*** Either reduce channel spacing using -o (current value is %d Hz),' % (options.if_offset) )
|
|
||||||
print ('*** or increase SDR output sample rate using -i (current rate is %d Hz)' % (options.if_rate) )
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
max_inputs = 1
|
|
||||||
|
|
||||||
from dv_tx import output_gains, gain_adjust, gain_adjust_fullrate, mod_adjust
|
|
||||||
|
|
||||||
if options.do_audio:
|
|
||||||
AUDIO = audio.source(options.alsa_rate, options.audio_input)
|
AUDIO = audio.source(options.alsa_rate, options.audio_input)
|
||||||
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
lpf_taps = filter.firdes.low_pass(1.0, options.alsa_rate, 3400.0, 3400 * 0.1, filter.firdes.WIN_HANN)
|
||||||
audio_rate = 8000
|
audio_rate = 8000
|
||||||
|
@ -158,73 +199,145 @@ class my_top_block(gr.top_block):
|
||||||
AUDIO_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
|
AUDIO_SCALE = blocks.multiply_const_ff(32767.0 * options.gain)
|
||||||
AUDIO_F2S = blocks.float_to_short()
|
AUDIO_F2S = blocks.float_to_short()
|
||||||
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
|
self.connect(AUDIO, AUDIO_DECIM, AUDIO_SCALE, AUDIO_F2S)
|
||||||
alt_input = AUDIO_F2S
|
audio_source = AUDIO_F2S
|
||||||
else:
|
else:
|
||||||
alt_input = None
|
audio_source = None
|
||||||
|
|
||||||
SUM = blocks.add_cc()
|
from dv_tx import output_gains, gain_adjust, gain_adjust_fullrate, mod_adjust
|
||||||
input_repeat = True
|
|
||||||
for i in range(len(protocols)):
|
for chan in self.channels:
|
||||||
SOURCE = blocks.file_source(gr.sizeof_short, options.file, input_repeat)
|
protocol = get_protocol(chan)
|
||||||
protocol = protocols[i]
|
if protocol is None:
|
||||||
if (options.fullrate_mode and protocol == 'ysf') or protocol == 'p25':
|
sys.stderr.write('failed to detect protocol, ignoring: %s\n' % (cfg))
|
||||||
gain_adj = gain_adjust_fullrate[protocols[i]]
|
continue
|
||||||
|
cfg = chan.config
|
||||||
|
dev = chan.device
|
||||||
|
modulator_rate = 48000 ## FIXME
|
||||||
|
bt = _def_bt
|
||||||
|
if 'bt' in cfg.keys():
|
||||||
|
bt = cfg['bt']
|
||||||
|
MOD = mod_pipeline_bc(
|
||||||
|
protocol = protocol,
|
||||||
|
output_gain = output_gains[protocol],
|
||||||
|
mod_adjust = mod_adjust[protocol],
|
||||||
|
if_freq = cfg['frequency'] - dev.frequency,
|
||||||
|
if_rate = dev.sample_rate,
|
||||||
|
sample_rate = modulator_rate,
|
||||||
|
bt = bt)
|
||||||
|
if cfg['source'].startswith('symbols:'):
|
||||||
|
filename = cfg['source'].split(':')[1]
|
||||||
|
source = blocks.file_source(gr.sizeof_char, filename, repeat=True)
|
||||||
|
self.connect(source, MOD, dev.get_sum_p())
|
||||||
else:
|
else:
|
||||||
gain_adj = gain_adjust[protocols[i]]
|
source = get_source(cfg, 'source', audio_source)
|
||||||
if protocols[i] == 'dmr':
|
source2 = get_source(cfg, 'source2', audio_source)
|
||||||
cfg = 'dmr-cfg.dat'
|
fullrate_mode = False ### TODO
|
||||||
elif protocols[i] == 'ysf':
|
if (fullrate_mode and protocol == 'ysf') or protocol == 'p25':
|
||||||
cfg = 'ysf-cfg.dat'
|
gain_adj = gain_adjust_fullrate[protocol]
|
||||||
elif protocols[i] == 'dstar':
|
else:
|
||||||
cfg = 'dstar-cfg.dat'
|
gain_adj = gain_adjust[protocol]
|
||||||
elif protocols[i].startswith('nxdn'):
|
cfgfile = None
|
||||||
cfg = 'nxdn-cfg.dat'
|
if protocol in 'dmr ysf dstar'.split():
|
||||||
|
cfgfile = '%s-cfg.dat' % (protocol)
|
||||||
|
elif protocol.startswith('nxdn'):
|
||||||
|
cfgfile = 'nxdn-cfg.dat'
|
||||||
|
CHANNEL = pipeline_sb(
|
||||||
|
protocol = protocol,
|
||||||
|
gain_adjust = gain_adj,
|
||||||
|
fullrate_mode = fullrate_mode,
|
||||||
|
alt_input = source2,
|
||||||
|
verbose = self.verbosity,
|
||||||
|
config_file = cfgfile)
|
||||||
|
self.connect(source, CHANNEL, MOD, dev.get_sum_p())
|
||||||
|
for dev in self.devices:
|
||||||
|
assert dev.sum_count > 0 # device must have at least one valid channel
|
||||||
|
dev.amp = blocks.multiply_const_cc(1.0 / float(dev.sum_count))
|
||||||
|
if dev.output_throttle is not None:
|
||||||
|
self.connect(dev.sum, dev.amp, dev.output_throttle, dev.u)
|
||||||
else:
|
else:
|
||||||
cfg = None
|
self.connect(dev.sum, dev.amp, dev.u)
|
||||||
this_freq = start_freq + i * options.if_offset
|
|
||||||
if_freq = this_freq - tune_freq
|
|
||||||
print ('%s\t%d\t%d\t%d' % (protocols[i], this_freq, tune_freq, if_freq))
|
|
||||||
CHANNEL = pipeline(
|
|
||||||
protocol = protocols[i],
|
|
||||||
output_gain = output_gains[protocols[i]],
|
|
||||||
gain_adjust = gain_adj,
|
|
||||||
mod_adjust = mod_adjust[protocols[i]],
|
|
||||||
if_freq = if_freq,
|
|
||||||
if_rate = options.if_rate,
|
|
||||||
sample_rate = options.modulator_rate,
|
|
||||||
bt = options.bt,
|
|
||||||
fullrate_mode = options.fullrate_mode,
|
|
||||||
alt_input = alt_input,
|
|
||||||
config_file = cfg)
|
|
||||||
self.connect(SOURCE, CHANNEL, (SUM, i))
|
|
||||||
|
|
||||||
self.u = osmosdr.sink (options.args)
|
def configure_devices(self, config):
|
||||||
AMP = blocks.multiply_const_cc(1.0 / float(len(protocols)))
|
self.devices = []
|
||||||
self.setup_sdr_output(options, tune_freq)
|
for cfg in config:
|
||||||
|
dev = device(cfg, self)
|
||||||
|
if cfg['args'].startswith('udp:'):
|
||||||
|
self.setup_udp_output(dev, cfg)
|
||||||
|
else:
|
||||||
|
self.setup_sdr_output(dev, cfg)
|
||||||
|
self.devices.append(dev)
|
||||||
|
|
||||||
self.connect(SUM, AMP, self.u)
|
def find_device(self, chan):
|
||||||
|
for dev in self.devices:
|
||||||
|
d = abs(chan['frequency'] - dev.frequency)
|
||||||
|
nf = dev.sample_rate // 2
|
||||||
|
if d + 6250 <= nf:
|
||||||
|
return dev
|
||||||
|
return None
|
||||||
|
|
||||||
def setup_sdr_output(self, options, tune_freq):
|
def configure_channels(self, config):
|
||||||
gain_names = self.u.get_gain_names()
|
self.channels = []
|
||||||
|
for cfg in config:
|
||||||
|
dev = self.find_device(cfg)
|
||||||
|
if dev is None:
|
||||||
|
sys.stderr.write('* * * Frequency %d not within spectrum band of any device - ignoring!\n' % cfg['frequency'])
|
||||||
|
continue
|
||||||
|
chan = channel(cfg, dev, self.verbosity, msgq=None)
|
||||||
|
self.channels.append(chan)
|
||||||
|
|
||||||
|
def setup_udp_output(self, dev, config):
|
||||||
|
dev.output_throttle = blocks.throttle(gr.sizeof_gr_complex, config['rate'])
|
||||||
|
hostinfo = config['args'].split(':')
|
||||||
|
hostname = hostinfo[1]
|
||||||
|
udp_port = int(hostinfo[2])
|
||||||
|
dev.u = blocks.udp_sink(gr.sizeof_gr_complex, hostname, udp_port)
|
||||||
|
|
||||||
|
def setup_sdr_output(self, dev, config):
|
||||||
|
dev.u = osmosdr.sink (config['args'])
|
||||||
|
gain_names = dev.u.get_gain_names()
|
||||||
for name in gain_names:
|
for name in gain_names:
|
||||||
range = self.u.get_gain_range(name)
|
range = dev.u.get_gain_range(name)
|
||||||
print ("gain: name: %s range: start %d stop %d step %d" % (name, range[0].start(), range[0].stop(), range[0].step()))
|
print ("gain: name: %s range: start %d stop %d step %d" % (name, range[0].start(), range[0].stop(), range[0].step()))
|
||||||
if options.gains:
|
if config['gains']:
|
||||||
for tuple in options.gains.split(","):
|
for t in config['gains'].split(","):
|
||||||
name, gain = tuple.split(":")
|
name, gain = t.split(":")
|
||||||
gain = int(gain)
|
gain = int(gain)
|
||||||
print ("setting gain %s to %d" % (name, gain))
|
print ("setting gain %s to %d" % (name, gain))
|
||||||
self.u.set_gain(gain, name)
|
dev.u.set_gain(gain, name)
|
||||||
|
|
||||||
print ('setting sample rate %d' % options.if_rate)
|
print ('setting sample rate %d' % config['rate'])
|
||||||
self.u.set_sample_rate(options.if_rate)
|
dev.u.set_sample_rate(config['rate'])
|
||||||
print ('setting SDR tuning frequency %d' % tune_freq)
|
print ('setting SDR tuning frequency %d' % config['frequency'])
|
||||||
self.u.set_center_freq(tune_freq)
|
dev.u.set_center_freq(config['frequency'])
|
||||||
self.u.set_freq_corr(options.frequency_correction)
|
dev.u.set_freq_corr(config['ppm'])
|
||||||
|
|
||||||
|
class tx_main(object):
|
||||||
|
def __init__(self):
|
||||||
|
parser = OptionParser(option_class=eng_option)
|
||||||
|
|
||||||
|
parser.add_option("-c", "--config-file", type="string", default=None, help="specify config file name")
|
||||||
|
parser.add_option("-v", "--verbosity", type="int", default=0, help="additional output")
|
||||||
|
parser.add_option("-p", "--pause", action="store_true", default=False, help="block on startup")
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
|
# wait for gdb
|
||||||
|
if options.pause:
|
||||||
|
print ('Ready for GDB to attach (pid = %d)' % (os.getpid(),))
|
||||||
|
raw_input("Press 'Enter' to continue...")
|
||||||
|
|
||||||
|
if options.config_file == '-':
|
||||||
|
config = json.loads(sys.stdin.read())
|
||||||
|
else:
|
||||||
|
config = json.loads(open(options.config_file).read())
|
||||||
|
self.tb = tx_block(options.verbosity, config = byteify(config))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.tb.run()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
self.tb.stop()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print ('Multiprotocol Digital Voice TX (C) Copyright 2017-2020 Max H. Parke KA1RBI')
|
print ('Multiprotocol Digital Voice TX (C) Copyright 2017-2020 Max H. Parke KA1RBI')
|
||||||
try:
|
tx = tx_main()
|
||||||
my_top_block().run()
|
tx.run()
|
||||||
except KeyboardInterrupt:
|
|
||||||
tb.stop()
|
|
||||||
|
|
Loading…
Reference in New Issue