diff --git a/CQWW_CW_2005.fs96k.cf7040.iq.s16.dat.xz b/CQWW_CW_2005.fs96k.cf7040.iq.s16.dat.xz new file mode 100644 index 000000000..3d7f42d2f Binary files /dev/null and b/CQWW_CW_2005.fs96k.cf7040.iq.s16.dat.xz differ diff --git a/config_webrx.py b/config_webrx.py index 34e480c6b..8d60a62a9 100644 --- a/config_webrx.py +++ b/config_webrx.py @@ -74,6 +74,8 @@ # samp_rate = 250000 samp_rate = 2400000 center_freq = 144250000 +start_freq = 0 +start_mod = "nfm" #nfm, am, lsb, usb, cw rf_gain = 5 #in dB. For an RTL-SDR, rf_gain=0 will set the tuner to auto gain mode, else it will be in manual gain mode. ppm = 0 @@ -102,15 +104,17 @@ ################################################################################################# # You can use other SDR hardware as well, by giving your own command that outputs the I/Q samples... Some examples of configuration are available here (default is RTL-SDR): +sdr_source = "IQ_file" # use one of: "rtl_sdr" "rx_sdr" "gr-osmosdr" "sound_card" "noise_source" "IQ_file" # >> RTL-SDR via rtl_sdr -start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) -format_conversion="csdr convert_u8_f" - -#lna_gain=8 -#rf_amp=1 -#start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain) -#format_conversion="csdr convert_s8_f" +if sdr_source == "rtl_sdr": + start_rtl_command="rtl_sdr -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) + format_conversion="csdr convert_u8_f" + + #lna_gain=8 + #rf_amp=1 + #start_rtl_command="hackrf_transfer -s {samp_rate} -f {center_freq} -g {rf_gain} -l{lna_gain} -a{rf_amp} -r-".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm, rf_amp=rf_amp, lna_gain=lna_gain) + #format_conversion="csdr convert_s8_f" """ To use a HackRF, compile the HackRF host tools from its "stdout" branch: git clone https://github.com/mossmann/hackrf/ @@ -127,19 +131,44 @@ # >> Sound card SDR (needs ALSA) # I did not have the chance to properly test it. -#samp_rate = 96000 -#start_rtl_command="arecord -f S16_LE -r {samp_rate} -c2 -".format(samp_rate=samp_rate) -#format_conversion="csdr convert_s16_f | csdr gain_ff 30" +if sdr_source == "sound_card": + samp_rate = 96000 + start_rtl_command="arecord -f S16_LE -r {samp_rate} -c2 -".format(samp_rate=samp_rate) + format_conversion="csdr convert_s16_f | csdr gain_ff 30" # >> /dev/urandom test signal source -# samp_rate = 2400000 -# start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate) -# format_conversion="csdr convert_u8_f" +if sdr_source == "noise_source": + samp_rate = 2400000 + start_rtl_command="cat /dev/urandom | (pv -qL `python -c 'print int({samp_rate} * 2.2)'` 2>&1)".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate) + format_conversion="csdr convert_u8_f" # >> Pre-recorded raw I/Q file as signal source -# You will have to correctly specify: samp_rate, center_freq, format_conversion in order to correctly play an I/Q file. -#start_rtl_command="(while true; do cat my_iq_file.raw; done) | csdr flowcontrol {sr} 20 ".format(sr=samp_rate*2*1.05) -#format_conversion="csdr convert_u8_f" +# You will have to correctly specify: samp_rate, center_freq, bytes_per_sample, format_conversion in order to correctly play an I/Q file. +if sdr_source == "IQ_file": + # select one: + #sample_file = "CQWW" + sample_file = "WSPR_demo" + + if sample_file == "CQWW": + file = "CQWW_CW_2005.fs96k.cf7040.iq.s16.dat" + samp_rate = 96000 + center_freq = 7040000 + bytes_per_sample = 4 + start_rtl_command="(while true; do cat {fn}; done) | csdr flowcontrol {sr} 20 ".format(fn=file,sr=samp_rate*bytes_per_sample*1.05) + format_conversion="csdr convert_s16_f --bigendian | csdr iq_swap_ff" + start_mod = "lsb" + + if sample_file == "WSPR_demo": + file = "wspr.fs12k.cf7040100.iq.f.dat" + samp_rate = 12000 + center_freq = 7040100 + bytes_per_sample = 8 + start_rtl_command="csdr wspr --file {fn} {sr} 20 ".format(fn=file,sr=samp_rate*bytes_per_sample*1.05) + format_conversion="" + start_mod = "cw" + start_freq = center_freq - 750 + + #format_conversion="csdr convert_u8_f" #>> The rx_sdr command works with a variety of SDR harware: RTL-SDR, HackRF, SDRplay, UHD, Airspy, Red Pitaya, audio devices, etc. # It will auto-detect your SDR hardware if the following tools are installed: @@ -147,12 +176,14 @@ # * the vendor-specific SoapySDR wrapper library, # * and SoapySDR itself. # Check out this article on the OpenWebRX Wiki: https://github.com/simonyiszk/openwebrx/wiki/Using-rx_tools-with-OpenWebRX/ -#start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) -#format_conversion="" +if sdr_source == "rx_sdr": + start_rtl_command="rx_sdr -F CF32 -s {samp_rate} -f {center_freq} -p {ppm} -g {rf_gain} -".format(rf_gain=rf_gain, center_freq=center_freq, samp_rate=samp_rate, ppm=ppm) + format_conversion="" # >> gr-osmosdr signal source using GNU Radio (follow this guide: https://github.com/simonyiszk/openwebrx/wiki/Using-GrOsmoSDR-as-signal-source) -#start_rtl_command="cat /tmp/osmocom_fifo" -#format_conversion="" +if sdr_source == "gr-osmosdr": + start_rtl_command="cat /tmp/osmocom_fifo" + format_conversion="" # ==== Misc settings ==== @@ -163,8 +194,8 @@ # - also increase the latency # - decrease the chance of audio underruns -start_freq = center_freq -start_mod = "nfm" #nfm, am, lsb, usb, cw +if start_freq == 0: + start_freq = center_freq iq_server_port = 4951 #TCP port for ncat to listen on. It will send I/Q data over its connections, for internal use in OpenWebRX. It is only accessible from the localhost by default. diff --git a/csdr.py b/csdr.py index a2fb4902b..f1e3252ba 100755 --- a/csdr.py +++ b/csdr.py @@ -62,6 +62,8 @@ def __init__(self): self.pipe_names=["bpf_pipe", "shift_pipe", "squelch_pipe", "smeter_pipe", "iqtee_pipe", "iqtee2_pipe"] self.secondary_pipe_names=["secondary_shift_pipe"] self.secondary_offset_freq = 1000 + self.secondary_dual_paths = True + self.secondary_demod_output_HTML = False def chain(self,which): any_chain_base="nc -v 127.0.0.1 {nc_port} | " @@ -79,7 +81,8 @@ def chain(self,which): chain_begin=any_chain_base+"csdr shift_addition_cc --fifo {shift_pipe} | csdr fir_decimate_cc {decimation} {ddc_transition_bw} HAMMING | csdr bandpass_fir_fft_cc --fifo {bpf_pipe} {bpf_transition_bw} HAMMING | csdr squelch_and_smeter_cc --fifo {squelch_pipe} --outfifo {smeter_pipe} 5 1 | " if self.secondary_demodulator: chain_begin+="csdr tee {iqtee_pipe} | " - chain_begin+="csdr tee {iqtee2_pipe} | " + if self.secondary_dual_paths == True: + chain_begin+="csdr tee {iqtee2_pipe} | " chain_end = "" if self.audio_compression=="adpcm": chain_end = " | csdr encode_ima_adpcm_i16_u8" @@ -87,20 +90,49 @@ def chain(self,which): elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr old_fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_s16"+chain_end - def secondary_chain(self, which): + def secondary_chain(self, demod, which): secondary_chain_base="cat {input_pipe} | " - if which == "fft": - return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "") - elif which == "bpsk31": - return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \ - "csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \ - "csdr simple_agc_cc 0.001 0.5 | " + \ - "csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \ - "CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \ - "CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8" - + if demod == "wspr": + if which == "fft": + fractional_decimate = self.if_samp_rate()/375.0 + integer_decimate = round(fractional_decimate) + if abs(fractional_decimate - integer_decimate) > 1e-15: + # FIXME: fractional_decimator_cc doesn't exist yet (needs to be derived from fractional_decimator_ff) + # so for now WSPR only works with the corresponding demo IQ sample file + secondary_chain_base += "csdr fractional_decimator_cc {decimate} | csdr wspr --fft {{iqtee2_pipe}} 1 ".format(decimate=fractional_decimate) + integer_decimate = 1 + secondary_chain_base += "csdr wspr --fft {{iqtee2_pipe}} {decimate} ".format(decimate=integer_decimate) + return secondary_chain_base + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "") + elif which == "demod": + # in this case the demod input_pipe will be connected to the iqtee2_pipe output from the FFT chain + #return "cat {input_pipe} | csdr wspr --demod" + return "cat {input_pipe}" + else: + if which == "fft": + return secondary_chain_base+"csdr realpart_cf | csdr fft_fc {secondary_fft_input_size} {secondary_fft_block_size} | csdr logpower_cf -70 " + (" | csdr compress_fft_adpcm_f_u8 {secondary_fft_size}" if self.fft_compression=="adpcm" else "") + elif which == "demod": + if demod == "bpsk31": + return secondary_chain_base + "csdr shift_addition_cc --fifo {secondary_shift_pipe} | " + \ + "csdr bandpass_fir_fft_cc $(csdr '=-(31.25)/{if_samp_rate}') $(csdr '=(31.25)/{if_samp_rate}') $(csdr '=31.25/{if_samp_rate}') | " + \ + "csdr simple_agc_cc 0.001 0.5 | " + \ + "csdr timing_recovery_cc GARDNER {secondary_samples_per_bits} 0.5 2 --add_q | " + \ + "CSDR_FIXED_BUFSIZE=1 csdr dbpsk_decoder_c_u8 | " + \ + "CSDR_FIXED_BUFSIZE=1 csdr psk31_varicode_decoder_u8_u8" + + # A "single path" secondary demodulator consists of samples sent to the FFT chain and the iqtee2_pipe setup + # as a bridge between the FFT and demod chains. No IQ samples are sent to the demod chain. + # Used when the demodulator needs to be a monolithic routine on a single chain but still produces + # FFT and demod text output. def set_secondary_demodulator(self, what): self.secondary_demodulator = what + if what == "wspr": + self.secondary_dual_paths = False + self.secondary_pipe_names = ["secondary_shift_pipe", "iqtee2_pipe"]; + self.secondary_demod_output_HTML = True + else: + self.secondary_dual_paths = True + self.secondary_pipe_names = ["secondary_shift_pipe"]; + self.secondary_demod_output_HTML = False def secondary_fft_block_size(self): return (self.samp_rate/self.decimation)/(self.fft_fps*2) #*2 is there because we do FFT on real signal here @@ -130,15 +162,17 @@ def secondary_bw(self): def start_secondary_demodulator(self): if(not self.secondary_demodulator): return print "[openwebrx] starting secondary demodulator from IF input sampled at %d"%self.if_samp_rate() - secondary_command_fft=self.secondary_chain("fft") - secondary_command_demod=self.secondary_chain(self.secondary_demodulator) + secondary_command_fft=self.secondary_chain(self.secondary_demodulator, "fft") + secondary_command_demod=self.secondary_chain(self.secondary_demodulator, "demod") self.try_create_pipes(self.secondary_pipe_names, secondary_command_demod + secondary_command_fft) secondary_command_fft=secondary_command_fft.format( \ input_pipe=self.iqtee_pipe, \ + iqtee2_pipe=self.iqtee2_pipe, \ secondary_fft_input_size=self.secondary_fft_size, \ secondary_fft_size=self.secondary_fft_size, \ secondary_fft_block_size=self.secondary_fft_block_size(), \ + if_samp_rate=self.if_samp_rate() ) secondary_command_demod=secondary_command_demod.format( \ input_pipe=self.iqtee2_pipe, \ @@ -176,7 +210,7 @@ def start_secondary_demodulator(self): def set_secondary_offset_freq(self, value): self.secondary_offset_freq=value - if self.secondary_processes_running: + if self.secondary_processes_running and self.secondary_shift_pipe != None: self.secondary_shift_pipe_file.write("%g\n"%(-float(self.secondary_offset_freq)/self.if_samp_rate())) self.secondary_shift_pipe_file.flush() @@ -200,6 +234,9 @@ def set_secondary_fft_size(self,secondary_fft_size): #to change this, restart is required self.secondary_fft_size=secondary_fft_size + def get_secondary_demod_output_HTML(self): + return self.secondary_demod_output_HTML + def set_audio_compression(self,what): self.audio_compression = what diff --git a/htdocs/index.wrx b/htdocs/index.wrx index 1be7dd760..027f5d784 100644 --- a/htdocs/index.wrx +++ b/htdocs/index.wrx @@ -107,6 +107,7 @@