From 4b3cc10924a0ff2b8ef530bf5d5d44b32fca32ac Mon Sep 17 00:00:00 2001 From: ha7ilm Date: Wed, 30 Sep 2015 14:06:30 +0000 Subject: [PATCH] Added some features. --- README.md | 20 +++++---- config_webrx.py | 9 +++- htdocs/index.wrx | 10 +++-- htdocs/openwebrx.js | 92 +++++++++++++++++++++++++++++++++----- openwebrx.py | 85 ++++++++++++++++++++++++++--------- plugins/dsp/csdr/plugin.py | 40 +++++++++++------ 6 files changed, 199 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 2e60081..f80c2f2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ It has the following features: - it works in Google Chrome, Chromium (above version 37) and Mozilla Firefox (above version 28), - currently only supports RTL-SDR, but other SDR hardware may be easily added. -**News:** +**News (2015-08-18)** - My BSc. thesis written on OpenWebRX is available here. - Several bugs were fixed to improve reliability and stability. - OpenWebRX now supports compression of audio and waterfall stream, so the required network uplink bandwidth has been decreased from 2 Mbit/s to about 200 kbit/s per client! (Measured with the default settings. It is also dependent on `fft_size`.) @@ -22,6 +22,13 @@ It has the following features: - Receivers can now be listed on sdr.hu. - License for OpenWebRX is now Affero GPL v3. +**News (2015-09-01)** +- The DDC in *csdr* has been hand-optimized for ARM NEON, so it runs 3× faster on the Raspberry Pi than before. +- Also we use *ncat* instead of *rtl_mus*, and it is also 3× faster. +- OpenWebRX now supports URLs like: http://localhost:8073/#freq=145555000,mod=usb + +- When upgrading OpenWebRX, please make sure that you upgrade *csdr*, and install the new (optional) dependency *ncat*! + ## Setup OpenWebRX currently requires Linux and python 2.7 to run. @@ -30,6 +37,7 @@ First you will need to install the dependencies: - libcsdr - rtl-sdr +- ncat (on Debian/Ubuntu, it is in the *nmap* package). *(It is optional, but highly advised.)* After cloning this repository and connecting an RTL-SDR dongle to your computer, you can run the server: @@ -57,14 +65,10 @@ However, if you hold down the shift key, you can drag the center line (BFO) or t ## Configuration tips -If you want to run OpenWebRX on a remote server instead of localhost, do not forget to set *server_hostname* in `config_webrx.py`, or you may get a WebSocket error. +Now we have a %[Wiki](https://github.com/simonyiszk/openwebrx/wiki) with some how-tos. However, some quick tips: + +If you want to run OpenWebRX on a remote server instead of localhost, do not forget to set *server_hostname* in `config_webrx.py`. DSP CPU usage can be fine-tuned in `plugins/dsp/csdr/plugin.py`: you can set transition bandwidths higher (thus degrade filter performance by decreasing the length of the kernel, but also decrease CPU usage), and also set `fft_size` lower. -If you constantly get *audio overrun* errors, you may change `audio_buffer_maximal_length_sec` in `openwebrx.js` from the default 1.7 to 3. - If you want a chat-box to the top of the page, here is a snippet for you to include in `config_webrx.py`. - -## Todo - -Currently, clients use up a lot of bandwidth. This will be improved later. diff --git a/config_webrx.py b/config_webrx.py index 59a85ab..9e1acce 100644 --- a/config_webrx.py +++ b/config_webrx.py @@ -66,6 +66,7 @@ sdrhu_public_listing = False dsp_plugin="csdr" fft_fps=9 fft_size=4096 +#samp_rate = 2048000 samp_rate = 250000 center_freq = 145525000 @@ -103,7 +104,13 @@ format_conversion="csdr convert_u8_f" shown_center_freq = center_freq #you can change this if you use an upconverter -client_audio_buffer_size = 4 +client_audio_buffer_size = 5 #increasing client_audio_buffer_size will: # - also increase the latency # - decrease the chance of audio underruns + +start_freq = center_freq +start_mod = "nfm" #nfm, am, lsb, usb, cw + +iq_server_port = 4951 +# (if ncat is not available on your system, rtl_mus will be used, thus you will have to set the same port as "my_listening_port" in config_rtl.py as well) diff --git a/htdocs/index.wrx b/htdocs/index.wrx index b993692..5cc9fe5 100644 --- a/htdocs/index.wrx +++ b/htdocs/index.wrx @@ -23,11 +23,13 @@ OpenWebRX | Open Source SDR Web App for Everyone! diff --git a/htdocs/openwebrx.js b/htdocs/openwebrx.js index 1deeaf7..7ea1c2e 100644 --- a/htdocs/openwebrx.js +++ b/htdocs/openwebrx.js @@ -959,6 +959,32 @@ function resize_waterfall_container(check_init) canvas_container.style.height=(window.innerHeight-e("webrx-top-container").clientHeight-e("openwebrx-scale-container").clientHeight).toString()+"px"; } + +audio_server_output_rate=11025; +audio_client_resampling_factor=4; + + +function audio_calculate_resampling(targetRate) +{ //both at the server and the client + output_range_max = 12000; + output_range_min = 8000; + i = 1; + while(true) + { + audio_server_output_rate = Math.floor(targetRate / i); + if(audio_server_output_rate < output_range_min) + { + audio_client_resampling_factor = audio_server_output_rate = 0; + divlog("Your audio card sampling rate ("+targetRate.toString()+") is not supported.
Please change your operating system default settings in order to fix this.",1); + } + if(audio_server_output_rate >= output_range_min && audio_server_output_rate <= output_range_max) break; //okay, we're done + i++; + } + audio_client_resampling_factor=i; + console.log("audio_calculate_resampling() :: "+audio_client_resampling_factor.toString()+", "+audio_server_output_rate.toString()); +} + + debug_ws_data_received=0; max_clients_num=0; @@ -974,6 +1000,7 @@ function on_ws_recv(evt) { var stringData=arrayBufferToString(evt.data); if(stringData.substring(0,16)=="CLIENT DE SERVER") divlog("Acknowledged WebSocket connection: "+stringData); + } if(firstChars=="AUD") { @@ -1010,6 +1037,7 @@ function on_ws_recv(evt) { case "setup": waterfall_init(); + audio_preinit(); break; case "bandwidth": bandwidth=parseInt(param[1]); @@ -1122,7 +1150,7 @@ var audio_initialized=0; var audio_received = Array(); var audio_buffer_index = 0; -var audio_resampler=new sdrjs.RationalResamplerFF(4,1); +var audio_resampler; var audio_codec=new sdrjs.ImaAdpcm(); var audio_compression="unknown"; var audio_node; @@ -1167,7 +1195,7 @@ function audio_prepare(data) audio_prepared_buffers.push(audio_rebuffer.take()); audio_buffer_current_count_debug++; } - if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) audio_buffering=false; + if(audio_buffering && audio_prepared_buffers.length>audio_buffering_fill_to) { console.log("buffers now: "+audio_prepared_buffers.length.toString()); audio_buffering=false; } } @@ -1246,6 +1274,8 @@ var audio_buffer_progressbar_update_disabled=false; var audio_buffer_total_average_level=0; var audio_buffer_total_average_level_length=0; +var audio_overrun_cnt = 0; +var audio_underrun_cnt = 0; function audio_buffer_progressbar_update() { @@ -1255,8 +1285,8 @@ function audio_buffer_progressbar_update() var overrun=audio_buffer_value>audio_buffer_maximal_length_sec; var underrun=audio_prepared_buffers.length==0; var text="buffer"; - if(overrun) text="overrun"; - if(underrun) text="underrun"; + if(overrun) { text="overrun"; console.log("audio overrun, "+(++audio_overrun_cnt).toString()); } + if(underrun) { text="underrun"; console.log("audio underrun, "+(++audio_underrun_cnt).toString()); } if(overrun||underrun) { audio_buffer_progressbar_update_disabled=true; @@ -1345,13 +1375,26 @@ function webrx_set_param(what, value) ws.send("SET "+what+"="+value.toString()); } -function audio_init() +function parsehash() { - audio_debug_time_start=(new Date()).getTime(); - audio_debug_time_last_start=audio_debug_time_start; + if(h=window.location.hash) + { + h.substring(1).split(",").forEach(function(x){ + harr=x.split("="); + console.log(harr); + if(harr[0]=="mod") starting_mod = harr[1]; + if(harr[0]=="freq") { + console.log(parseInt(harr[1])); + console.log(center_freq); + starting_offset_frequency = parseInt(harr[1])-center_freq; + } + }); + + } +} - //https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js - audio_initialized=1; // only tell on_ws_recv() not to call it again +function audio_preinit() +{ try { window.AudioContext = window.AudioContext||window.webkitAudioContext; @@ -1362,6 +1405,28 @@ function audio_init() divlog('Your browser does not support Web Audio API, which is required for WebRX to run. Please upgrade to a HTML5 compatible browser.', 1); } + //we send our setup packet + + parsehash(); + //needs audio_context.sampleRate to exist + + audio_calculate_resampling(audio_context.sampleRate); + audio_resampler = new sdrjs.RationalResamplerFF(audio_client_resampling_factor,1); + ws.send("SET output_rate="+audio_server_output_rate.toString()+" action=start"); //now we'll get AUD packets as well + +} + +function audio_init() +{ + if(audio_client_resampling_factor==0) return; //if failed to find a valid resampling factor... + + audio_debug_time_start=(new Date()).getTime(); + audio_debug_time_last_start=audio_debug_time_start; + + //https://github.com/0xfe/experiments/blob/master/www/tone/js/sinewave.js + audio_initialized=1; // only tell on_ws_recv() not to call it again + + //on Chrome v36, createJavaScriptNode has been replaced by createScriptProcessor createjsnode_function = (audio_context.createJavaScriptNode == undefined)?audio_context.createScriptProcessor.bind(audio_context):audio_context.createJavaScriptNode.bind(audio_context); audio_node = createjsnode_function(audio_buffer_size, 0, 1); @@ -1379,7 +1444,14 @@ function audio_init() audio_buffer = audio_context.createBuffer(xhr.response, false); audio_source.buffer = buffer; audio_source.noteOn(0);*/ - demodulator_analog_replace('nfm'); //needs audio_context.sampleRate to exist + demodulator_analog_replace(starting_mod); + if(starting_offset_frequency) + { + demodulators[0].offset_frequency = starting_offset_frequency; + demodulators[0].set(); + mkscale(); + } + //hide log panel in a second (if user has not hidden it yet) window.setTimeout(function(){ if(typeof e("openwebrx-panel-log").openwebrxHidden == "undefined" && !was_error) diff --git a/openwebrx.py b/openwebrx.py index 62ff800..38b53b6 100755 --- a/openwebrx.py +++ b/openwebrx.py @@ -47,7 +47,6 @@ import ctypes #import rtl_mus import rxws import uuid -import config_webrx as cfg import signal import socket @@ -75,11 +74,17 @@ class MultiThreadHTTPServer(ThreadingMixIn, HTTPServer): pass def handle_signal(signal, frame): + global spectrum_dsp print "[openwebrx] Ctrl+C: aborting." + cleanup_clients(True) + spectrum_dsp.stop() os._exit(1) #not too graceful exit +rtl_thread=spectrum_dsp=server_fail=None + def main(): - global clients, clients_mutex, pypy, lock_try_time, avatar_ctime + global clients, clients_mutex, pypy, lock_try_time, avatar_ctime, cfg + global serverfail, rtl_thread print print "OpenWebRX - Open Source SDR Web App for Everyone! | for license see LICENSE file in the package" print "_________________________________________________________________________________________________" @@ -87,6 +92,10 @@ def main(): print "Author contact info: Andras Retzler, HA7ILM " print + no_arguments=len(sys.argv)==1 + if no_arguments: print "[openwebrx] Configuration script not specified. I will use: \"config_webrx.py\"" + cfg=__import__("config_webrx" if no_arguments else sys.argv[1]) + #Set signal handler signal.signal(signal.SIGINT, handle_signal) #http://stackoverflow.com/questions/1112343/how-do-i-capture-sigint-in-python @@ -108,13 +117,18 @@ def main(): #Start rtl thread if cfg.start_rtl_thread: - rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=()) + rtl_thread=threading.Thread(target = lambda:subprocess.Popen(cfg.start_rtl_command, shell=True), args=()) rtl_thread.start() - print "[openwebrx-main] Started rtl thread: "+cfg.start_rtl_command + print "[openwebrx-main] Started rtl_thread: "+cfg.start_rtl_command #Run rtl_mus.py in a different OS thread python_command="pypy" if pypy else "python2" - rtl_mus_thread=threading.Thread(target = lambda:subprocess.Popen(python_command+" rtl_mus.py config_rtl", shell=True), args=()) + rtl_mus_cmd = python_command+" rtl_mus.py config_rtl" + if os.system("ncat --version > /dev/null") != 32512: + print "[openwebrx-main] ncat detected, using it instead of rtl_mus:" + rtl_mus_cmd = "ncat localhost 8888 | ncat -4l %d -k --send-only --allow 127.0.0.1 " % cfg.iq_server_port + print rtl_mus_cmd + rtl_mus_thread=threading.Thread(target = lambda:subprocess.Popen(rtl_mus_cmd, shell=True), args=()) rtl_mus_thread.start() # The new feature in GNU Radio 3.7: top_block() locks up ALL python threads until it gets the TCP connection. print "[openwebrx-main] Started rtl_mus." time.sleep(1) #wait until it really starts @@ -207,10 +221,20 @@ def mutex_watchdog_thread_function(): print "[openwebrx-watchdog] Mutex unlock timeout. Locker: \""+str(clients_mutex_locker)+"\" Now unlocking..." clients_mutex.release() time.sleep(0.5) - + +def check_server(): + global spectrum_dsp, server_fail, rtl_thread + if server_fail: return server_fail + #print spectrum_dsp.process.poll() + if spectrum_dsp and spectrum_dsp.process.poll()!=None: server_fail = "spectrum_thread dsp subprocess failed" + #if rtl_thread and not rtl_thread.is_alive(): server_fail = "rtl_thread failed" + if server_fail: print "[openwebrx-check_server] >>>>>>> ERROR:", server_fail + return server_fail + def spectrum_thread_function(): - global clients - dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() + global clients, spectrum_dsp + spectrum_dsp=dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() + dsp.nc_port=cfg.iq_server_port dsp.set_demodulator("fft") dsp.set_samp_rate(cfg.samp_rate) dsp.set_fft_size(cfg.fft_size) @@ -220,6 +244,7 @@ def spectrum_thread_function(): sleep_sec=0.87/cfg.fft_fps print "[openwebrx-spectrum] Spectrum thread initialized successfully." dsp.start() + dsp.read(8) #dummy read to skip bufsize & preamble print "[openwebrx-spectrum] Spectrum thread started." bytes_to_read=int(dsp.get_fft_bytes_to_read()) while True: @@ -255,16 +280,17 @@ def get_client_by_id(client_id, use_mutex=True): def log_client(client, what): print "[openwebrx-httpd] client {0}#{1} :: {2}".format(client.ip,client.id,what) -def cleanup_clients(): - # if client doesn't open websocket for too long time, we drop it +def cleanup_clients(end_all=False): + # - if a client doesn't open websocket for too long time, we drop it + # - or if end_all is true, we drop all clients global clients cma("cleanup_clients") correction=0 for i in range(0,len(clients)): i-=correction #print "cleanup_clients:: len(clients)=", len(clients), "i=", i - if (not clients[i].ws_started) and (time.time()-clients[i].gen_time)>45: - print "[openwebrx] cleanup_clients :: client timeout to open WebSocket" + if end_all or ((not clients[i].ws_started) and (time.time()-clients[i].gen_time)>45): + if not end_all: print "[openwebrx] cleanup_clients :: client timeout to open WebSocket" close_client(i, False) correction+=1 cmr() @@ -363,13 +389,12 @@ class WebRXHandler(BaseHTTPRequestHandler): # ========= Initialize DSP ========= dsp=getattr(plugins.dsp,cfg.dsp_plugin).plugin.dsp_plugin() - dsp.set_samp_rate(cfg.samp_rate) - dsp.set_demodulator("nfm") - dsp.set_offset_freq(0) - dsp.set_bpf(-4000,4000) + dsp_initialized=False dsp.set_audio_compression(cfg.audio_compression) dsp.set_format_conversion(cfg.format_conversion) - dsp.start() + dsp.set_offset_freq(0) + dsp.set_bpf(-4000,4000) + dsp.nc_port=cfg.iq_server_port myclient.dsp=dsp while True: @@ -378,8 +403,9 @@ class WebRXHandler(BaseHTTPRequestHandler): break # ========= send audio ========= - temp_audio_data=dsp.read(256) - rxws.send(self, temp_audio_data, "AUD ") + if dsp_initialized: + temp_audio_data=dsp.read(256) + rxws.send(self, temp_audio_data, "AUD ") # ========= send spectrum ========= while not myclient.spectrum_queue.empty(): @@ -417,9 +443,17 @@ class WebRXHandler(BaseHTTPRequestHandler): dsp.set_offset_freq(int(param_value)) elif param_name=="mod": if (dsp.get_demodulator()!=param_value): - dsp.stop() + if dsp_initialized: dsp.stop() dsp.set_demodulator(param_value) + if dsp_initialized: dsp.start() + elif param_name == "output_rate": + if not dsp_initialized: + dsp.set_output_rate(int(param_value)) + dsp.set_samp_rate(cfg.samp_rate) + elif param_name=="action" and param_value=="start": + if not dsp_initialized: dsp.start() + dsp_initialized=True else: print "[openwebrx-httpd:ws] invalid parameter" if bpf_set: @@ -464,6 +498,13 @@ class WebRXHandler(BaseHTTPRequestHandler): data=f.read() extension=self.path[(len(self.path)-4):len(self.path)] extension=extension[2:] if extension[1]=='.' else extension[1:] + checkresult=check_server() + if extension == "wrx" and checkresult: + self.send_response(500) + self.send_header('Content-type','text/html') + self.end_headers() + self.wfile.write("

OpenWebRX Internal Server Error

Please check the server log for details.") + return if extension == "wrx" and ((self.headers['user-agent'].count("Chrome")==0 and self.headers['user-agent'].count("Firefox")==0 and (not "Googlebot" in self.headers['user-agent'])) if 'user-agent' in self.headers.keys() else True) and (not request_param.count("unsupported")): self.send_302("upgrade.html") return @@ -492,7 +533,9 @@ class WebRXHandler(BaseHTTPRequestHandler): ("%[RX_ADMIN]",cfg.receiver_admin), ("%[RX_ANT]",cfg.receiver_ant), ("%[RX_DEVICE]",cfg.receiver_device), - ("%[AUDIO_BUFSIZE]",str(cfg.client_audio_buffer_size)) + ("%[AUDIO_BUFSIZE]",str(cfg.client_audio_buffer_size)), + ("%[START_OFFSET_FREQ]",str(cfg.start_freq-cfg.center_freq)), + ("%[START_MOD]",cfg.start_mod) ) for rule in replace_dictionary: while data.find(rule[0])!=-1: diff --git a/plugins/dsp/csdr/plugin.py b/plugins/dsp/csdr/plugin.py index 1fcda24..5432fdb 100644 --- a/plugins/dsp/csdr/plugin.py +++ b/plugins/dsp/csdr/plugin.py @@ -44,13 +44,15 @@ class dsp_plugin: self.demodulator = "nfm" self.name = "csdr" self.format_conversion = "csdr convert_u8_f" + self.base_bufsize = 512 + self.nc_port = 4951 try: subprocess.Popen("nc",stdout=subprocess.PIPE,stderr=subprocess.PIPE).kill() except: print "[openwebrx-plugin:csdr] error: netcat not found, please install netcat!" def chain(self,which): - any_chain_base="nc -v localhost 4951 | "+self.format_conversion+(" | " if self.format_conversion!="" else "")+"csdr flowcontrol {flowcontrol} 10 | " + any_chain_base="nc -v localhost {nc_port} | csdr setbuf {start_bufsize} | csdr through | "+self.format_conversion+(" | " if self.format_conversion!="" else "") ##"csdr flowcontrol {flowcontrol} auto 1.5 10 | " if which == "fft": fft_chain_base = "sleep 1; "+any_chain_base+"csdr fft_cc {fft_size} {fft_block_size} | csdr logpower_cf -70 | csdr fft_exchange_sides_ff {fft_size}" if self.fft_compression=="adpcm": @@ -61,9 +63,9 @@ class dsp_plugin: chain_end = "" if self.audio_compression=="adpcm": chain_end = " | csdr encode_ima_adpcm_i16_u8" - if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff | csdr convert_f_i16"+chain_end + if which == "nfm": return chain_begin + "csdr fmdemod_quadri_cf | csdr limit_ff | csdr fractional_decimator_ff {last_decimation} | csdr deemphasis_nfm_ff 11025 | csdr fastagc_ff 1024 | csdr convert_f_i16"+chain_end elif which == "am": return chain_begin + "csdr amdemod_cf | csdr fastdcblock_ff | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16"+chain_end - elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr limit_ff | csdr convert_f_i16"+chain_end + elif which == "ssb": return chain_begin + "csdr realpart_cf | csdr fractional_decimator_ff {last_decimation} | csdr agc_ff | csdr clipdetect_ff | csdr limit_ff | csdr convert_f_i16"+chain_end def set_audio_compression(self,what): self.audio_compression = what @@ -92,6 +94,10 @@ class dsp_plugin: def get_output_rate(self): return self.output_rate + def set_output_rate(self,output_rate): + self.output_rate=output_rate + self.set_samp_rate(self.samp_rate) #as it depends on output_rate + def set_demodulator(self,demodulator): #to change this, restart is required self.demodulator=demodulator @@ -153,10 +159,16 @@ class dsp_plugin: self.mkfifo(self.shift_pipe) #run the command - command=command_base.format(bpf_pipe=self.bpf_pipe,shift_pipe=self.shift_pipe,decimation=self.decimation,last_decimation=self.last_decimation,fft_size=self.fft_size,fft_block_size=self.fft_block_size(),bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(),ddc_transition_bw=self.ddc_transition_bw(),flowcontrol=int(self.samp_rate*4*2*1.5)) + command=command_base.format( bpf_pipe=self.bpf_pipe, shift_pipe=self.shift_pipe, decimation=self.decimation, \ + last_decimation=self.last_decimation, fft_size=self.fft_size, fft_block_size=self.fft_block_size(), \ + bpf_transition_bw=float(self.bpf_transition_bw)/self.if_samp_rate(), ddc_transition_bw=self.ddc_transition_bw(), \ + flowcontrol=int(self.samp_rate*2), start_bufsize=self.base_bufsize*self.decimation, nc_port=self.nc_port) + print "[openwebrx-dsp-plugin:csdr] Command =",command #code.interact(local=locals()) - self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp) + my_env=os.environ.copy() + my_env["CSDR_DYNAMIC_BUFSIZE_ON"]="1"; + self.process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setpgrp, env=my_env) self.running = True #open control pipes for csdr and send initialization data @@ -179,14 +191,16 @@ class dsp_plugin: # os.killpg(self.process.pid, signal.SIGTERM) # # time.sleep(0.1) - try: - os.unlink(self.bpf_pipe) - except: - print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe - try: - os.unlink(self.shift_pipe) - except: - print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe + if self.bpf_pipe: + try: + os.unlink(self.bpf_pipe) + except: + print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe + if self.shift_pipe: + try: + os.unlink(self.shift_pipe) + except: + print "[openwebrx-dsp-plugin:csdr] stop() :: unlink failed: " + self.bpf_pipe self.running = False def restart(self):