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 @@
@@ -163,7 +164,12 @@
- + + +
+
+
+
diff --git a/htdocs/openwebrx.css b/htdocs/openwebrx.css index 9e75d8441..830dc53a1 100644 --- a/htdocs/openwebrx.css +++ b/htdocs/openwebrx.css @@ -896,6 +896,37 @@ img.openwebrx-mirror-img 100%{ transform: rotateX(360deg) rotateY(720deg); } } +#openwebrx-digimode-content div#openwebrx-digimode-status +{ + width: 100px; + height: 20px; +} + +#openwebrx-digimode-status +{ + display: inline-block; + border-radius: 5px; + color: white; + text-align: center; + font-size: 8pt; + font-weight: bold; + text-shadow: 0px 0px 4px #000000; + cursor: default; + user-select: none; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +#openwebrx-digimode-status-text +{ + position: relative; + top: 3px; + width: inherit; +} + #openwebrx-digimode-content { word-wrap: break-word; diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index f26afadc7..94c1b1974 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -174,6 +174,15 @@ function updateWaterfallColors(which) waterfall_min_level=parseInt(wfmin.value); waterfall_max_level=parseInt(wfmax.value); } + +function waterfallColorsSet(min, max) +{ + waterfall_min_level=min; + waterfall_max_level=max; + e("openwebrx-waterfall-color-min").value=waterfall_min_level.toString(); + e("openwebrx-waterfall-color-max").value=waterfall_max_level.toString(); +} + function waterfallColorsDefault() { waterfall_min_level=waterfall_min_level_default; @@ -435,6 +444,14 @@ function demodulator_default_analog(offset_frequency,subtype) this.low_cut=-4000; this.high_cut=4000; } + else if(subtype=="wspr") + { + var WSPR_BFO = 750, WSPR_BW=375+50; + var hbw = Math.round(WSPR_BW/2); + this.low_cut=WSPR_BFO-hbw; + this.high_cut=WSPR_BFO+hbw; + this.server_mod="ssb"; + } this.wait_for_timer=false; this.set_after=false; @@ -567,7 +584,7 @@ function mkenvelopes(visible_range) //called from mkscale { demodulators[i].envelope.draw(visible_range); } - if(demodulators.length) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut); + if(demodulators.length && secondary_waterfall_zoom) secondary_demod_waterfall_set_zoom(demodulators[0].low_cut, demodulators[0].high_cut); } function demodulator_remove(which) @@ -1139,6 +1156,7 @@ function audio_calculate_resampling(targetRate) debug_ws_data_received=0; max_clients_num=0; +sdr_source=''; var COMPRESS_FFT_PAD_N=10; //should be the same as in csdr.c @@ -1169,7 +1187,12 @@ function on_ws_recv(evt) { //alert("Yupee! Doing FFT"); //if(first4Chars=="FFTS") console.log("FFTS"); - if(fft_compression=="none") waterfall_add_queue(new Float32Array(evt.data,4)); + if(fft_compression=="none") + { + var waterfall_f32=new Float32Array(evt.data,4); + if(first4Chars=="FFTS") secondary_demod_waterfall_add_queue(waterfall_f32); //TODO digimodes + else waterfall_add_queue(waterfall_f32); + } else if(fft_compression="adpcm") { fft_codec.reset(); @@ -1187,6 +1210,11 @@ function on_ws_recv(evt) secondary_demod_push_data(arrayBufferToString(evt.data).substring(4)); //console.log("DAT"); } + else if(first4Chars=="HTML") + { + secondary_demod_push_HTML(arrayBufferToString(evt.data).substring(4)); + //console.log("HTML"); + } else if(first3Chars=="MSG") { /*try @@ -1216,6 +1244,7 @@ function on_ws_recv(evt) break; case "secondary_setup": secondary_demod_init_canvases(); + secondary_demod_data_clear(); break; case "if_samp_rate": if_samp_rate=parseInt(param[1]); @@ -1249,6 +1278,9 @@ function on_ws_recv(evt) smeter_level=parseFloat(param[1]); setSmeterAbsoluteValue(smeter_level); break; + case "sdr_source": + sdr_source=param[1]; + break; } } /*} @@ -1723,6 +1755,20 @@ function waterfall_mkcolor(db_value, waterfall_colors_arg) return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain); } +function secondary_waterfall_mkcolor(db_value, waterfall_colors_arg, max_level, min_level) +{ + if(typeof waterfall_colors_arg === 'undefined') waterfall_colors_arg = waterfall_colors; + if(db_valuesecondary_waterfall_max_level) db_value=secondary_waterfall_max_level; + full_scale=secondary_waterfall_max_level-secondary_waterfall_min_level; + relative_value=db_value-secondary_waterfall_min_level; + value_percent=relative_value/full_scale; + percent_for_one_color=1/(waterfall_colors_arg.length-1); + index=Math.floor(value_percent/percent_for_one_color); + remain=(value_percent-percent_for_one_color*index)/percent_for_one_color; + return color_between(waterfall_colors_arg[index+1],waterfall_colors_arg[index],remain); +} + function color_between(first, second, percent) { output=0; @@ -2454,6 +2500,11 @@ function demodulator_analog_replace_last() { demodulator_analog_replace(last_ana secondary_demod = false; secondary_demod_offset_freq = 0; secondary_demod_waterfall_queue = []; +secondary_demod_canvas_container_height = 50; +secondary_demod_show_marker = true; +secondary_waterfall_zoom = true; +secondary_waterfall_max_level = 0; +secondary_waterfall_min_level = 0; function demodulator_digital_replace_last() { @@ -2462,16 +2513,42 @@ function demodulator_digital_replace_last() } function demodulator_digital_replace(subtype) { - switch(subtype) - { - case "bpsk31": - case "rtty": - secondary_demod_start(subtype); - demodulator_analog_replace('usb', true); - demodulator_buttons_update(); - break; - } - toggle_panel("openwebrx-panel-digimodes", true); + secondary_demod_canvas_container_height = 50; + secondary_demod_show_marker = true; + secondary_waterfall_zoom = true; + secondary_waterfall_max_level = waterfall_max_level; + secondary_waterfall_min_level = waterfall_min_level; + $("#openwebrx-cursor-blink").show(); + $("#openwebrx-digimode-status").hide(); + + switch(subtype) + { + case "bpsk31": + case "rtty": + secondary_demod_start(subtype); + demodulator_analog_replace('usb', true); + demodulator_buttons_update(); + break; + case "wspr": + secondary_demod_canvas_container_height = 100; + secondary_demod_show_marker = false; + secondary_waterfall_zoom = false; + secondary_waterfall_max_level = 6; + secondary_waterfall_min_level = -33; + secondary_demod_start(subtype); + demodulator_analog_replace('wspr', true); + demodulator_buttons_update(); + $("#openwebrx-cursor-blink").hide(); + $("#openwebrx-digimode-status-text").empty(); + $("#openwebrx-digimode-status").show(); + $("#openwebrx-digimode-select-channel").css("opacity","0"); + if (sdr_source == "IQ_file") + waterfallColorsSet(-145, -10); + zoom_max_level_hps = 1; + mkzoomlevels(); + break; + } + toggle_panel("openwebrx-panel-digimodes", true); } function secondary_demod_create_canvas() @@ -2494,6 +2571,7 @@ function secondary_demod_remove_canvases() function secondary_demod_init_canvases() { + $("#openwebrx-digimode-content-container").height(secondary_demod_canvas_container_height); secondary_demod_remove_canvases(); secondary_demod_canvases=[]; secondary_demod_canvases.push(secondary_demod_create_canvas()); @@ -2578,12 +2656,43 @@ function secondary_demod_push_data(x) if(y==" ") return " "; return y; }).join(""); - $("#openwebrx-cursor-blink").before(""+x+""); + $("#openwebrx-digimode-text").append(""+x+""); +} + + +// Interesting issue: If you append a single-character-at-a-time to the innerHTML then the characters are +// NOT evaluated for HTML tags. So we buffer up a string and append in one go when there is a line break. +// Should probably also use a timeout to accomodate output without a line break. +// This happens here because secondary_demod_push_HTML() is currently called with single character input. + +var secondary_demod_push_HTML_s = ''; +var wspr_status = [ "Idle", "Sync", "Running", "Decoding" ]; +var wspr_status_color = [ 'lightSkyBlue', 'violet', '#00aba6', '#ff6262' ]; // last two are OpenWebRX progress bar colors + +function secondary_demod_push_HTML(x) +{ + var update = (x == '\n'); + x = x.replace(/\n/g, "
"); + + var status = x.charCodeAt(0); + if (status >= 1 && status <= 4) { + $("#openwebrx-digimode-status-text").html(wspr_status[status-1]); + $("#openwebrx-digimode-status").css("background-color", wspr_status_color[status-1]); + return; + } + + secondary_demod_push_HTML_s += encodeURIComponent(x); + if (update) { + $("#openwebrx-digimode-HTML").append(decodeURIComponent(secondary_demod_push_HTML_s)); + secondary_demod_push_HTML_s = ''; + } } function secondary_demod_data_clear() { - $("#openwebrx-cursor-blink").prevAll().remove(); + $("#openwebrx-digimode-text").empty(); + $("#openwebrx-digimode-HTML").empty(); + secondary_demod_push_HTML_s = ''; } function secondary_demod_close_window() @@ -2603,7 +2712,7 @@ function secondary_demod_waterfall_add(data) var oneline_image = secondary_demod_current_canvas_context.createImageData(w,1); for(x=0;x>>0)>>((3-i)*8))&0xff; } @@ -2642,6 +2751,9 @@ function secondary_demod_listbox_changed() case "rtty": demodulator_digital_replace('rtty'); break; + case "wspr": + demodulator_digital_replace('wspr'); + break; } } @@ -2688,7 +2800,8 @@ function secondary_demod_update_channel_freq_from_event(evt) secondary_demod_mousedown=false; function secondary_demod_canvas_container_mousein() { - $("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px"); + if (secondary_demod_show_marker) + $("#openwebrx-digimode-select-channel").css("opacity","0.7"); //.css("border-width", "1px"); } function secondary_demod_canvas_container_mouseout() diff --git a/openwebrx.py b/openwebrx.py index 07c91e1de..89226d63f 100755 --- a/openwebrx.py +++ b/openwebrx.py @@ -143,6 +143,13 @@ def main(): break except: pass + + if cfg.sdr_source == "IQ_file" and os.path.isfile(cfg.file) == False: + print "decompressing IQ data file:", cfg.file + if os.system("xz -d {0}.xz".format(cfg.file)) == 32512: + print "You need to install the 'xz' utility to decompress the %s.xz IQ data file" % cfg.file + print "tukaani.org/xz has 32/64-bit x86 binaries of xz for Windows" + return #Start rtl thread if os.system("csdr 2> /dev/null") == 32512: #check for csdr @@ -457,7 +464,7 @@ def do_GET(self): return myclient.ws_started=True #send default parameters - rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients)) + rxws.send(self, "MSG center_freq={0} bandwidth={1} fft_size={2} fft_fps={3} audio_compression={4} fft_compression={5} max_clients={6} sdr_source={7} setup".format(str(cfg.shown_center_freq),str(cfg.samp_rate),cfg.fft_size,cfg.fft_fps,cfg.audio_compression,cfg.fft_compression,cfg.max_clients,cfg.sdr_source)) # ========= Initialize DSP ========= dsp=csdr.dsp() @@ -534,7 +541,7 @@ def do_GET(self): myclient.loopstat=423 if len(secondary_demod_data) == 0: break # print "len(secondary_demod_data)", len(secondary_demod_data), secondary_demod_data #TODO digimodes - rxws.send(self, secondary_demod_data, "DAT ") + rxws.send(self, secondary_demod_data, "DAT " if dsp.get_secondary_demod_output_HTML() == False else "HTML") except: break # ========= process commands ========= diff --git a/wspr.fs12k.cf7040100.iq.f.dat.xz b/wspr.fs12k.cf7040100.iq.f.dat.xz new file mode 100644 index 000000000..eff9115e9 Binary files /dev/null and b/wspr.fs12k.cf7040100.iq.f.dat.xz differ